본문 바로가기
FE Development/React

React 밑바닥(webpack & babel) 부터 세팅하기

by 개발자 데이빗 2022. 8. 29.

웹팩이란 ?

자바스크립트 어플리케이션을 위한 정적 모듈 번들러
의존성이 있는 모듈들을 하나의 파일로 통합시켜준다.
한 웹페이지에서 사용하는 여러개의 자바스크립트를 동시에 가져와서 생기는 네트워크 병목현상 방지한다.
모듈 단위로 개발할 수 있어 유지보수성을 높일 수 있다.

 

바벨이란 ?

최신 자바스크립트를 사용할 수 있게 하는 트랜스파일러

바벨은 히브리어로 '혼돈'이란 뜻이라고 성경에 나타나 있다.

자바스크립트의 문법은 빠르게 진화하고 있지만 정작 자바스크립트 코드를 실행해주는 환경은 이를 지원해주지 못하는 경우가 많다.

구약성경에서 바벨탑 건설 중 언어가 뒤섞여 혼돈 속에 바벨 탑 건설을 그만두었던 일화처럼 다양한 웹 브라우저, node.js 등 각기 다른 환경에서 혼돈에 빠지지 않고 자바스크립트 코드가 잘 돌아가도록 자바스크립트 코드를 번역해주는 역할은 한다.

 

웹팩 설정

기본 속성

  1. mode : 운영, 개발환경 구분에 사용된다.
  2. entry: 웹팩 번들링을 하기 위한 최초의 진입점 경로이다.
  3. output: 웹팩 번들링의 결과물을 반환할 경로와 파일이름이다.
  4. resolve: 번들링 할 파일의 확장자이다.
  5. loader: 웹팩은 기본적으로 js, json만 빌드가 가능하다. 이외의 다른 소스를 빌드하도록 지원하는 속성이다.
  6. plugins: 웹팩의 기본 동작 이외의 추가 기능을 제공한다.
const mode = process.env.NODE_ENV || 'development';

module.exports = {
  mode,
  entry: {
    main: './src/index.tsx',
  },
  output: {
    path: path.resolve('./dist'),
    filename: '[name].js',
  },
  resolve: {
    extensions: ['.js', '.jsx', '.ts', '.tsx'],
  },
}

자주 사용되는 웹팩 플러그인

아래와 같은 방식으로 플러그인을 사용한다.

  plugins: [
    new webpack.BannerPlugin({
      banner: `
            Build Date: ${new Date().toLocaleString()}
            Commit Version: ${childProcess.execSync('git rev-parse --short HEAD')}
            Author: ${childProcess.execSync('git config user.name')}
            `,
    }),
    new webpack.DefinePlugin({}),
    new HtmlWebpackPlugin({
      template: './public/index.html',
      templateParameters: {
        env: process.env.NODE_ENV === 'development' ? '(개발용)' : '',
      },
      minify: process.env.NODE_ENV === 'production' ? {
        collapseWhitespace: true,
        removeComments: true,
      } : false,
    }),
    new CleanWebpackPlugin(),
    ...(process.env.NODE_ENV === 'production'
      ? [new MiniCssExtractPlugin({ filename: '[name].css' })] : []),
  ],
  1. BannerPlugin
    • 빌드 정보에 사용된다.
    new webpack.BannerPlugin({banner: ''})
    childProcess.execSync('git rev-parse --short HEAD')
    childProcess.execSync('git config user.name')```
  2. DefinePlugin
    • 환경 의존적인 정보를 사용하기 위해서 사용된다. (root url)
    new webpack.DefinePlugin({
        SOME_STRING: JSON.stringify('some string')
        'api.domain': 'http://dev.api.domain.com'
    })
    console.log(SOME_STRING)
    console.log(process.env.NODE_ENV)
  3. HtmlTemplatePlugin
  • html을 처리하는데 사용되며 서드파티 플러그인
    new HtmlWebpackPlugin({
        template: './src/index.html',
        templateParameters: {
            env: process.env.NODE_ENV === 'development' ? '(개발용)' : ''
        },
        minify: process.env.NODE_ENV === 'production' ? {
            collapseWhitespace: true,
            removeComments: true,
        } : false,
    }),
  1. CleanWebpackPlugin
    • 빌드시 기존의 파일을 지우고 새로 빌드하게 해주는 플러그인
  2. MiniCssExtractPlugin
    • 스타일 시트가 점점 많아지면 하나의 자바스크립트 결과물로 만드는 것이 부담 스타일 시트 코드만 뽑아서 별도의 css 파일로 만들어 파일을 분리하는 것이 좋다 여러 개의 작은 파일을 동시에 다운로드 하는 것보다 빠르다.
    ...(process.env.NODE_ENV === 'production' ? 
    [new MiniCssExtractPlugin({filename: '[name].css'})] : [])

 

로더(loader)

아래와 같은 방식으로 로더를 사용한다.

  module: {
    rules: [
      {
        test: /\.(s[ac]ss|css)$/,
        use: [
          process.env.NODE_ENV === 'production'
            ? MiniCssExtractPlugin.loader : 'style-loader',
          'css-loader',
          'sass-loader',
        ],
      },
      {
        test: /\.(png|jpg|gif|svg)$/,
        type: 'asset/resource',
      },
      {
        test: /\.js$/,
        loader: 'babel-loader',
        exclude: /node_modules/,
      },
      {
        test: /\.(ts|tsx)$/,
        loader: 'ts-loader',
        exclude: /node_modules/,
      },
    ],
  },

 

  1. css loader, sass loader, style loader
    • import로 불러온 css와 sass 파일을 하나의 css 파일로 묶어서 빌드해준다
    • style loader는 css파일은 <style></style> 태그 안에 넣어 cssom 트리를 만들 수 있도록 해준다.
  {
    test: /\.(s[ac]ss|css)$/,
    use: [
      process.env.NODE_ENV === 'production'
        ? MiniCssExtractPlugin.loader : 'style-loader',
      'css-loader',
      'sass-loader',
    ],
  },
  1. file loader, url loader
    • file loader : 빌드시 파일을 복사하여 빌드파일 생성
    • url loader : limit 이내의 크기의 파일은 빌드파일을 생성하지 않고 base64 형식으로 번들안에 직접 넣어준다.
    • webpack5 부터는 file-loader,url-loader를 기본적으로 제공, asset/resource 타입을 지정해 사용한다.
    • 자세한 설정은 webpack docs를 참고
  {
      test: /\.(png|jpg|gif|svg)$/,
      loader: 'file-loader',
      options: {
          publicPath: './dist/',
          name: '[name],[ext]?[hash]',
      },
  },
  {
      test: /\.(png|jpg|gif|svg)$/,
      loader: 'url-loader',
      options: {
          publicPath: './dist/',
          name: '[name],[ext]?[hash]',
          limit: 2000,
      },
  },
  {
    test: /\.(png|jpg|gif|svg)$/,
    type: 'asset/resource',
  },

 

바벨 설정

바벨의 빌드 과정

  1. 파싱 (Parsing)
  2. 변환 (Transforming) - 플러그인이 담당
  3. 출력 (Printing)

바벨 플러그인

  1. @babel/plugin-transform-block-scoping const, let -> var 로 변경해준다.
  2. @babel/plugin-transform-arrow-functions arrow function -> function()
  3. @babel/plugin-transform-strict-mode strict mode를 추가 해준다.
  4. 프리셋 플러그인을 통합, 아래와 같은 방식으로 사용된다.
module.exports = {
 plugins: [
     '@babel/plugin-transform-block-scoping',
     '@babel/plugin-transform-arrow-functions',
     '@babel/plugin-transform-strict-mode'
 ]
 }

하지만 실제로는 유용한 바벨 플러그인을 한번에 제공하는 프리셋들이 있으므로 프리셋을 사용하도록 한다.

module.exports = {
  presets: [
    '@babel/preset-env',
    '@babel/preset-react',
  ],
};

 

웹팩 개발 서버

  • 개발환경에서도 운영환경과 유사한 환경을 갖춤으로써 배포시 잠재적 문제 미리 확인할 수 있다.
  • ajax 방식의 api 연동은 cors 정책 때문에 반드시 개발서버가 필요하다.
  • contentBase: 정적 파일을 제공할 경로, 기본값은 웹팩 아웃풋
  • publicPath: 브라우저를 통해 접근하는 경로. 기본값은 '/' 이다.
  • host: 개발환경에서 도메인을 맞추어야 하는 상황에서 사용
  • overlay: 빌드시 에러나 경고를 브라우저에 표시
  • port: 개발서버 포트 설정,
  • stats: 메시지 수준을 설정 , 'none', 'errors-only', 'minimal', 'normal', 'verbose',로 수준을 졸절
  • historyApiFallBack: 히스토리 api를 사용하는 spa 개발시 설정, 404가 발생하면 index.html로 리다이렉트한다.
  • onBeforeSetupMiddleware: 개발용 mock api 설정
  • proxy: 서버 프록싱 설정
  • 개발서버 실행시 --progress를 추가하면 빌드 진행율을 보여준다.
  devServer: {
    client: {
      overlay: true,
    },
    onBeforeSetupMiddleware: (devServer) => {
      if (!devServer) {
        throw new Error('webpack-dev-server is not defined');
      }
      devServer.app.use(apiMocker('/apis', 'mocks/apis'));
      devServer.app.get('/apis/users', (req, res) => {
        // res.header('Access-Control-Allow-Origin', '*');
        res.json();
      });
    },
    proxy: {
      '/apis': 'http://localhost:8080',
    },
    //hot: true,
    liveReload: false,
  },

핫로딩

  • 전체 화면이 아닌 바뀐 화면만 리프레시 해줌으로서 개발을 용이하게 함
  • devServer에 hot:true를 추가하여 사용한다. (default 값이 true)

최적화

  1. mode를 통한 최적화
  2. optimization 속성을 통한 최적화 (webpack5에서는 자동으로 된다.)
    • 수동 설정 방법
optimization: {
    minimizer: mode === 'production' ? [
        new OptimizeCSSAssertsPlugin()
    ] : []
}
  1. splutChunks
// entry가 여러개인 경우 중복을 통합
splitChunks: {
  chunks: 'all',
},
  1. externals
    • 웹팩 빌드 시간 단축
    • 이미 빌드된 패키지를 사용할 시 다시 빌드하지 않고 node_modules에서 가져올 수 있도록 설정
    • copy-webpack-plugin을 통해 node_modules를 빌드폴더에 복사
  externals: {
    axios: 'axios',
  },

타입스크립트 설정

  1. typescript, ts-node 설치
  2. tsconfig.json 설정
  3. ts-loader를 webpack.config.js에 추가
// tsconfig.json
{
    "compilerOptions": {
        "strict": true,
        "module": "commonjs",
        "moduleResolution": "node",
        "lib": ["es2020", "dom"],
        "target": "ES5",
        "outDir": "./dist",
        "esModuleInterop": true,
        "allowJs": true,
        "jsx": "react",
        "typeRoots": ["src/types"],
        "skipLibCheck": true,
        "types": ["react/next", "react-dom/next"],
    },
    "exclude": ["node_modules", "dist"],
    "include": [
        "src",
        "index.js",
        "webpack.config.js",
        "babel.config.js",
        ".eslintrc.js",
        ".prettierrc.js",
    ]
}

webpack docs 참고

https://webpack.kr/guides/typescript/

 

TypeScript | 웹팩

웹팩은 모듈 번들러입니다. 주요 목적은 브라우저에서 사용할 수 있도록 JavaScript 파일을 번들로 묶는 것이지만, 리소스나 애셋을 변환하고 번들링 또는 패키징할 수도 있습니다.

webpack.kr

 

ES-Lint, Prettier 설정

개인적으로 airbnb 린트 설정을 선호해서 airbnb 린트 설정 방법을 기준으로 정리한다.

1. 아래 명령어를 통해 필요한 패키지의 버전정보를 확인한다.

npm info "eslint-config-airbnb@latest" peerDependencies

2. 또는 아래 명령어를 통해 필요한 패키지를 설치한다

npx install-peerdeps --dev eslint-config-airbnb

3. 아래 명령어를 통해 타입스크립트 관련 패키지도 설치한다.

npm install --save-dev eslint @typescript-eslint/eslint-plugin @typescript-eslint/parser
npm install --save-dev eslint-config-airbnb-typescript

4. .eslintrc.js

module.exports = {
  parser: '@typescript-eslint/parser',
  plugins: ['@typescript-eslint'],
  extends: [
    'plugin:@typescript-eslint/recommended',
    'eslint-config-prettier',
    'airbnb',
    'airbnb/hooks',
    'airbnb-typescript',
  ],
  parserOptions: {
    project: './tsconfig.json',
  },
  ignorePatterns: ['/dist'],
  rules: {},
};

5. 아래 명령어를 통해 프리티어 관련 패키지를 설치한다.

npm install --save-dev prettier eslint-config-prettier eslint-plugin-prettier

6. .prettierrc.js 까지 작성하면 끝!

module.exports = {
    bracketSpacing: false,
    bracketSameLine: false,
    singleQuote: true,
    trailingComma: 'all',
    arrowParens: 'avoid',
}

 

리액트 설치

1. react와 react-dom 패키지를 설치한다.

npm install --save react react-dom

 

2. index 파일 작성, 여기서 App은 앱의 엔트리 파일이며 리액트 18버전부터는 아래와 같이 createRoot메소드를 사용하여야 한다.

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './app';

const rootElement = document.getElementById('root');
if (!rootElement) throw new Error('Failed to find the root element');
const root = ReactDOM.createRoot(rootElement);

root.render(<App />);

3. index.html 파일에 root id 추가

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document<%= env%></title>
</head>

<body>
    <!-- 여기를 추가 -->
    <div id="root"></div>
</body>

</html>

 

사용된 버전

    "@babel/cli": "^7.18.10",
    "@babel/core": "^7.18.13",
    "@babel/preset-env": "^7.18.10",
    "@babel/preset-react": "^7.18.6",
    "@types/react": "^18.0.17",
    "@types/react-dom": "^18.0.6",
    "@typescript-eslint/eslint-plugin": "^5.13.0",
    "@typescript-eslint/parser": "^5.0.0",
    "babel-loader": "^8.2.5",
    "clean-webpack-plugin": "^4.0.0",
    "connect-api-mocker": "^1.10.0",
    "css-loader": "^6.7.1",
    "eslint": "^8.22.0",
    "eslint-config-airbnb": "^19.0.4",
    "eslint-config-airbnb-typescript": "^17.0.0",
    "eslint-config-prettier": "^8.5.0",
    "eslint-plugin-import": "^2.26.0",
    "eslint-plugin-jsx-a11y": "^6.6.1",
    "eslint-plugin-react": "^7.31.0",
    "eslint-plugin-react-hooks": "^4.6.0",
    "file-loader": "^6.2.0",
    "html-webpack-plugin": "^5.5.0",
    "husky": "^8.0.0",
    "lint-staged": "^13.0.3",
    "mini-css-extract-plugin": "^2.6.1",
    "sass-loader": "^13.0.2",
    "style-loader": "^3.3.1",
    "terser-webpack-plugin": "^5.3.5",
    "ts-loader": "^9.3.1",
    "typescript": "^4.7.4",
    "url-loader": "^4.1.1",
    "webpack": "^5.74.0",
    "webpack-cli": "^4.10.0",
    "webpack-dev-server": "^4.10.0"

댓글