티스토리 뷰

이전 세팅하기 1에서 빌드하는 방법을 배웠다. 

하지만 이 번들된 js파일은 변경 사항이 생기면 다시 빌드를 해야 변경 사항이 적용된다. 

 

CRA로 react 프로젝트를 만들었을 때는 hot reloading(변경 사항을 바로 반영하여 dev환경에서 바로 확인가능한 기능)세팅이 이미 되어있어 따로 처리할 필요가 없었는데 수동으로 할 경우엔 webpack.config에 추가해줘야 한다.

 

webpack-dev-server  
:hot reloading 을 위해서 추가로 설치해야하는 웹팩 라이브러리

🔺공식 홈페이지에서 global 설치보다는 -D 를 권장하여 local로 사용하길 바란다고 써있다.

웹팩 컨피그를 수정하기 전에 위의 라이브러리를 dev 환경에 설치해준다. 

yarn add -D webpack-dev-server @types/webpack-dev-server
  • dev server 는 hot reloading 기능뿐만 아니라 proxy서버 기능도 제공한다. CORS에러를 이 라이브러리를 통해서 해결할 수 있다는 소리이다.
  • 타입스크립트를 사용하기때문에 type들도 같이 설치해준다.

hot-reloading 을 위해 추가적으로 설치해야하는 플러그인

이전 글인 1에서 에러가 났던 부분인데 hot-reloading 기능을 사용하려면 2가지를 추가적으로 또 설치해야 한다. 

yarn add @pmmmwh/react-refresh-webpack-plugin react-refresh

 

이후, webpack config에서 preset과 같은 레벨에 env를 추가해준다.

webpack.config.json

import path from 'path';
import ReactRefreshWebpackPlugin from '@pmmmwh/react-refresh-webpack-plugin';
import webpack, { Configuration as WebpackConfiguration } from 'webpack';
import { Configuration as WebpackDevServerConfiguration } from 'webpack-dev-server';
import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';

interface Configuration extends WebpackConfiguration {
  devServer?: WebpackDevServerConfiguration;
}

import ForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin';

const isDevelopment = process.env.NODE_ENV !== 'production';

const config: Configuration = {
  name: 'my-front',
  mode: isDevelopment ? 'development' : 'production',
  devtool: !isDevelopment ? 'hidden-source-map' : 'eval',
  resolve: {
    extensions: ['.js', '.jsx', '.ts', '.tsx', '.json'],
    alias: {
      //alias에 들어가는 경로와 tsconfig에서도 같이 설정해줘야 한다
      '@hooks': path.resolve(__dirname, 'hooks'),
      '@components': path.resolve(__dirname, 'components'),
      '@layouts': path.resolve(__dirname, 'layouts'),
      '@pages': path.resolve(__dirname, 'pages'),
      '@utils': path.resolve(__dirname, 'utils'),
      '@typings': path.resolve(__dirname, 'typings'),
    },
  },
  entry: {
    app: './client',
  },
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        loader: 'babel-loader',
        options: {
          presets: [
            [
              '@babel/preset-env',
              {
                targets: { browsers: ['IE 10'] },
                debug: isDevelopment,
              },
            ],
            '@babel/preset-react',
            '@babel/preset-typescript',
          ],
          env: {
            development: {
              plugins: [require.resolve('react-refresh/babel')],
            },
          },
        },
        exclude: path.join(__dirname, 'node_modules'),
      },
      {
        test: /\.css?$/,
        use: ['style-loader', 'css-loader'],
      },
    ],
  },
  plugins: [
    new ForkTsCheckerWebpackPlugin({
      async: false,
      // eslint: {
      //   files: "./src/**/*",
      // },
    }),
    new webpack.EnvironmentPlugin({ NODE_ENV: isDevelopment ? 'development' : 'production' }),
  ],
  output: {
    path: path.join(__dirname, 'dist'),
    filename: '[name].js', // 대괄호에 들어가는 name은 상단의 entry의 키값과 동일하다. app.js
    publicPath: '/dist/',
  },
  devServer: {
    historyApiFallback: true, // react router
    port: 3090,
    devMiddleware: { publicPath: '/dist/' },
    static: { directory: path.resolve(__dirname) },
    proxy: {
      '/api/': {
        target: 'http://localhost:3095',
        changeOrigin: true,
      },
    },
  },
};

//개발 환경에서 쓸 plugin
if (isDevelopment && config.plugins) {
  config.plugins.push(new webpack.HotModuleReplacementPlugin());
  config.plugins.push(new ReactRefreshWebpackPlugin());
  config.plugins.push(new BundleAnalyzerPlugin({ analyzerMode: 'server', openAnalyzer: true }));
}

//개발환경이 아닐때 (배포 환경)
if (!isDevelopment && config.plugins) {
  config.plugins.push(new webpack.LoaderOptionsPlugin({ minimize: true }));
  config.plugins.push(new BundleAnalyzerPlugin({ analyzerMode: 'static' }));
}

export default config;

세부 플러그인 추가 설명

위 코드에서 fork-ts-checker-webpack-plugin은 무슨 기능을 하나?

import ForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin';
  • 타입스크립트 검사를 할 때, blocking식으로 검사를 한다.(블로킹? 다음 동작을 막는 것 즉, 하나하나 순차적으로 실행)
  • 이 플러그인을 쓰면 `타입스크립트 체킹`과 `웹팩 실행`이 동시에 동작할 수 있도록 해준다.(병렬처리를 하는 건지.. 그래서 성능적으로 더 좋다) 
  • 간단히 말하면 TS랑 webpack이랑 동시에 작업할 수 있게 해주는 플러그인

+ BundleAnalyzerPlugin가 자동으로 열려서 불편하다면, `openAnalyzer: false`로 변경해두고 필요할 때 true로 변경하면 된다. (어차피 dev server를 키면 log에 번들 애널라이저를 어느 url에서 확인할 수 있다고 알려준다.)

아래는 log 예시

Using polyfills: No polyfills were added, since the `useBuiltIns` option was not set.
Webpack Bundle Analyzer is started at http://127.0.0.1:8888

 

다시 본문으로 돌아와서 웹팩 config의 devServer 세팅을 보자 

devServer: {
    historyApiFallback: true, // react router
    port: 3090,
    devMiddleware: { publicPath: '/dist/' },
    static: { directory: path.resolve(__dirname) },
    proxy: {
      '/api/': {
        target: 'http://localhost:3095',
        changeOrigin: true,
      },
    },
  },
  • historyApiFallback 옵션은 react router을 위한 옵션이다.
  • port는 프론트 서버를 말한다. (front devserver port)
  • publicPath의 경로는 index.html의 secript src와 path가 동일해야 한다. 
    • 즉, 번들된 app.js를 바로 실행시킬 때에는 index.html의 script src를  ./dist/app.js로 해주고 프론트의 devServer에서 실행시킬 때에는 /dist/app.js로 .점을 빼서 publicPath와 경로를 맞춰야 한다.
    • index.html 을 바로 실행시킬 때에는 .을 붙이고 devServer통해서 할 때에는 .을 뺀다.
  • 나머지는 proxy관련 세팅이다.

devServer 세팅을 했으니 shortcut을 package.json에 추가해준다. 

+ 2023 03 기준 script의 명령어가 변경되어 보니 이전 버전과 명령어가 아래와 같은 이유로 변경되었다고 한다. 

TS_NODE_PROJECT랑 tsconfig-for-webpack-config.json 모두 옛날 설정 방식입니다. 이제는 웹팩이 typescript를 지원해서 webpack serve --env development만 해도 됩니다.

Q. dev 명령어에서 cross-env가 빠진이유?
cross-env는 TS_NODE_PROJECT process.env 설정을 위해 넣었던 것이라 이제는 필요 없습니다. 다만 npm run build를 위해서 설치는 해야 합니다.

출처: 인프런 제로초 slack 클론 코딩

package.json의 명령어는 아래와 같이 설정해주면 된다.

"scripts": {
    "dev": "webpack serve --env development",
    "build": "cross-env NODE_ENV=production webpack",
    "test": "echo \"Error: no test specified\" && exit"
  },

 

[에러발생]

 

이후 개발자도구 로그를 보면 뭔가 제대로 돌아가지 않는 느낌(동작은 한다.)이었는데 에러 메세지를 보니 react 17이하버전이 아니라 18버전을 사용하고 있어 index.ts(제 코드의 경우 client.ts)의 react 메서드가 현재 사용하고있는 react 18버전과 매칭되지 않는다는 에러였다. => 콘솔을 보면 에러 메세지와 함께 친절하게 어떻게 고쳐야할 지 알려주는 docs까지 링크를 걸어놨다.

 

아래와 같이 수정해준다.

 

client.ts (= index.ts) 

import { createRoot } from 'react-dom/client';
import React from 'react';
import App from '@layouts/App';
const container = document.getElementById('app');
const root = createRoot(container!); // createRoot(container!) if you use TypeScript
root.render(<App />);
  • 타입스크립트의 경우 container 요소가 항상 있다는 걸 표현하기 위해 "!(느낌표)"를 뒤에 붙여주면 타입에러가 사라진다.
댓글