나는 오늘도 멋있다

React-webpack 시리즈 - 3 (dev, common, prod) 본문

Web/WebPack

React-webpack 시리즈 - 3 (dev, common, prod)

나는 오늘도 멋있다 2023. 11. 14. 17:18

너무 오랜만에 글을 작성한다. 가족여행을 갔다와서 글을 작성하기전에 이것저것 테스트해보고 확인을하는데
해결하다보니 시간이 오래걸렸다. 시간이 좀 지나다보니 지날글에서 했던것들을 간단히 정리해보고 가려한다.

 

지난글들에서는 webpack을 이용하여 개발서버를 열었고, babel-loader를 사용하여 preset - react,typescript,js등 설정하고,
css또 적용하였다. 하지만 현재는 많은것들이 추가되어 작성한 코드를 토대로 리뷰하는 형식으로 작성하려한다.

 

1. webpack.dev.js

const path = require('path');
// HTML파일 생성을 단순화하며, 나만의 템플릿을 제공하도록 할수있다.
const HtmlWebpackPlugin = require('html-webpack-plugin');
// TypeScript 타입체크를 별도의 프로세스로 분리하여 속도를 높인다.
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');


module.exports = {
    // webpack에 내장된 최적화 기능을 사용할 수 있다. dev or prod
    mode: "development",
    // 웹팩의 출발지점 설정
    entry: path.resolve(__dirname,'../src/index.tsx'),
    // 번들링된 파일을 디버깅하기 위한 설정
    devtool: "eval-source-map",
    // 개발시 사용하는 서버설정
    /*
        devServer를 이용하여 실행할때는, 메모리 상에서 번들된 파일을 제공하므로,
        devtool이 개발 서버에도 적용이 된다. 또한 output의 publicPath는 기본적으로 루트("/")로 설정되어 있다.
        즉, 개발 모드에서의 output은 필수가 아니며, 배포나 테스트등 을 위해 빌드할때는 output 설정이 필요하다
    */
    devServer: {
        // 개발서버 실행후 브라우저 open여부
        open: false,
        // 애플리케이션의 상태를 유지하고 변경된 부분만 업데이트
        hot: true,
        // 개발서버가 열링 Port번호
        port: 3000,
        /*
        SPA에서 브라우저 히스토리와 라우팅을 관리하는데 유용하며,
        사용자가 잘못된 경로로 이동하였을경우 클라이언트측에서 라우팅을 처리하고
        적절한 화면을 표시하기 위함
        */
        historyApiFallback: true,
        // 파일 변경이 감지되면 페이지를 다시 로드하고 새로고침
        liveReload: true,
    },
   // webpack은 기본적으로 JS, JSON만 해석할수있다. 즉 다른 유형의 모듈을 처리하는 방법을 결정
    module:{
        /*
        rules: 모듈이 생성될때 요청과 일치하는 배열, 로더를 모듈에 적용시키거나 파서를 수정할수있다.
        1. test: 어떤파일을 변환할지 지정하는 속성
        2. use: 파일을 변환할때 어떤 로더를 사용해야하는지
        3. exclude: 외부모듈을 제외한다. 번들파일의 크기를줄이고, 빌드성능을 향상 즉, 필요한 모듈만 사용하기위해
        */
        rules:[
            {
                test: /\.(ts|tsx|js|jsx)$/,
                use: "ts-loader",
                exclude: /node_modules/,
            },
            {
                test: /\.js$/,
                use: "babel-loader",
                exclude: /node_modules/,
            },
            {
                test: /\.css$/,
                use: ["style-loader","css-loader"]
            },
        ]
    }, 
    // webpack 빌드 프로세스중 필요한 작업을 수행하는 객체(파일) 플러그인 적용
    plugins: [
        new HtmlWebpackPlugin({
            template: "public/index.html"
        }),
        new ForkTsCheckerWebpackPlugin()
    ],
    // 모듈을 해석하는 방식을 변경할수있다.
    resolve:{
        // 확장자를 순서대로 해석하며 앞에서부터 해석하고 남은것은 해석하지 않는다.
        extensions:[".js", ".ts", ".jsx", ".tsx", ".css", ".json"],
        // 절대경로 지정
        alias: {
            "@components": path.resolve("/src/components")
          },
    }
}

 

저번코드와는 다르게 플러그인이 추가, 로더추가, 절대경로 지정 등이 추가되었다.

-  ts-loader의 추가

ts-loader를 추가를 하게된 이유는 babel-loader를 이용하여 devserver를 실행하면 tsconfig.json의 설정이 적용되지 않는 문제 때문에 추가하게 되었다. 이말은 build시에도 적용되지 않는 문제였고, 해당이유를 찾아보니 babel-loader를 이용하면 타입을 검사하지 않는 문제가 있었다. 이러한 이유로 ts-loader를 babel-loader와 같이 사용하게 되었다. 

// babel.config.json

{
    "presets": [
      "@babel/preset-env"
    ],
    "plugins": [
      [
        "@babel/plugin-transform-runtime",
        {
         "corejs": 3
        }
      ]
    ]
  }

위와 같이 babel의 설정도 필요없는 것을 삭제해줬다.

 

-  ForkTsCheckerWebpackPlugin 추가

해당 플러그인은  babel-loader의 문제점을 찾는 중에 알게되었다. 내가 직접 찾은것은 아니지만 형의권유로 테스트를 해보았다.
ts-loader를 사용하지 않아도 해당 플러그인을 사용하면 babel-loader를 사용하더라도 tsconfig 설정에 맞게 타입 검사를 수행한다.
그렇다면 ts-loader를 사용하지 않고 babel-loader와 사용하면 되지 않나? 라는 생각을 했지만, ts-loader와 같이 사용하면 많은 이점들 있었다. 병렬 타입 검사로 빌드 속도를 높일수 있었고, DevServer에서 실시간 타입검사, 타입 검사와 번들링이 분리되므로 불필요한 빌드 중단방지, 타입검사 오류를 놓지지 않게 하는등 여러 이규가 있었다. 그래서 ts-loader와 같이 사용하게 되었다.

 

2. webpack.prod.js

// Js의 경로 모듈
const path = require('path');
// Webpack 번들을 포함하는 HTML 파일을 생성 한다.
const HtmlWebpackPlugin = require("html-webpack-plugin");
// Css를 포함한 JS파일별로 각css파일을 생성한다.
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
// Css를 압축하고 파일크기를 최소화 한다. 공백제거, CSS 규칙제거
const CssMinimizerWebpackPlugin = require("css-minimizer-webpack-plugin");
// Js 를 압축하고 파일크기를 최소화 한다. 불필요한 공백제거, 미사용코드 제거
const TerserWebpackPlugin = require("terser-webpack-plugin");


module.exports = {
    // webpack에 내장된 최적화 기능을 사용할 수 있다. dev or prod
    mode: "production",
    // 웹팩의 출발지점을 설정(최상위 파일) - 다중 지정가능
    entry: {
        main: path.resolve(__dirname, '../src/index.tsx'),
    },
    // 번들링된 파일을 디버깅하기 위한 설정
    devtool: "source-map",

    // 번들, 애셋 및 webpack으로 번들링하거나 로드하는 기타 항목을 출력하는 방법과 위치를 webpack에 지시하는 옵션
    output: {
        // 번들 파일의 이름을 정의한다.
        // name: 파일의 이름을 기반으로 만들어진다. index=main으로 정의되어 있음
        // contenthash: 파일의 해쉬태그를 붙인다. 브라우저 캐싱, 파일버전관리
        filename:"[name].[contenthash].js",
        // 번들링된 폴더의 위치와 이름지정
        path: path.resolve(__dirname, "../dist"),
        //  브라우저에서 해당 자산을 로드할 때 사용되는 경로를 지정
        publicPath: './',
        // 번들링하기전 이전 output디렉터리를 정리
        clean: true
    },
    // webpack은 기본적으로 JS, JSON만 해석할수있다. 즉 다른 유형의 모듈을 처리하는 방법을 결정
    module:{
        /*
        rules: 모듈이 생성될때 요청과 일치하는 배열, 로더를 모듈에 적용시키거나 파서를 수정할수있다.
        1. test: 어떤파일을 변환할지 지정하는 속성
        2. use: 파일을 변환할때 어떤 로더를 사용해야하는지
        3. exclude: 외부모듈을 제외한다. 번들파일의 크기를줄이고, 빌드성능을 향상 즉, 필요한 모듈만 사용하기위해
        */
        rules:[
            {
                test: /\.(ts|tsx|js|jsx)$/,
                use: "ts-loader",
                exclude: /node_modules/,
            },
            {
                test: /\.css$/,
                // style-loader대신에 MiniCssExtractPlugin이  들어간이유는?
                // style-loader는 개발시 편리한 디버깅을 하기위해서이다. 스타일 시트가 동적으로 페이지에 적용되므로
                // 수정된 스타일이 즉시 적용되어 개발자가 펴하게 작업할 수 있다. 하지만 prod에서는
                // 디자인이 쉽게 변하지 안을 뿐더러, 해당 플러그인을 사용하여 css파일을 별도로 추출하여
                // 캐싱하고 병렬로 다운로드할 수 있게 합니다. 이로써 웹페이지 로딩 시간을 단축하고 성능을 향상시킴
                use: [MiniCssExtractPlugin.loader, "css-loader"]
            }
        ]
    },
    // 최적화 관련 설정을 정의 하는 객체
    optimization: {
        // 사용하지 않는 export모듈을 번들에 포함하지않음
        usedExports: true,
        // minimizer에 지정된 플러그인을 실행시킨다.
        minimize: true,
        // minimize 가 false로 지정되면 플러그인들은 적용되지 않는다.
        minimizer: [
            // TerserWebpackPlugin은 서드파티이다. 그렇기때문에 webpack 공식문서는 많은 내용은없다.
            // https://github.com/terser/terser#minify-options (TerserWebpackPlugin git)
            // https://terser.org/docs/ (TerserWebpackPlugin 공식문서)
            new TerserWebpackPlugin({
                // 다양한 압축 옵션 및 최적화를 설정하는 객체
                terserOptions: {
                    // 코드 압축관련구성
                  compress: {
                    // console.log 삭제
                    drop_console: true,
                  },
                  // 압축코드의 출력형식 구성
                  output: {
                    // 모든 주석제거
                    comments: false
                  }
                },
              }),
            new CssMinimizerWebpackPlugin(),
        ],
        // 코드 분할을 적용하도록 최적화 설정
        splitChunks:{
            // 반복되는 코드를 따로 분리하여 재사용성을 높이는데 도움을줌
            chunks: "all"
        }
    },
    // webpack 빌드 프로세스중 필요한 작업을 수행하는 객체(파일)
    plugins: [
        new HtmlWebpackPlugin({
            template: "public/index.html"
        }),

        new MiniCssExtractPlugin()
    ],
    // 모듈을 해석하는 방식을 변경할수있다.
    resolve: {
        // 확장자를 순서대로 해석하며 앞에서부터 해석하고 남은것은 해석하지 않는다.
        extensions: ['.js', '.ts', '.jsx', '.tsx', '.css', 'json'],
        // 절대경로 지정
        alias: {
            "@components": path.resolve("/src/components")
          },
    }



}

 

webpack.dev와 같은 설정이 많다. prod설정에서는 최적화 설정이 추가되었다. 이부분은 블로그, 여러문서 들은 참고해서 작성했다.

주석을 다달고 있기때문에 크게 설명할 필요는 없는것 같다. 

 

3. tsconfig.json

{
  "compilerOptions": {
    // 컴파일 할 ECMAScript 버전을 설정
    "target": "ESNEXT",
    // 코드 내에서 사용되는 모듈시스템 설정
    "module": "commonjs",
    // CommonJS와 ES 모듈 간의 상호 운용성을 제공(즉, commonjs에서도 import 구문을 사용할수 있게)
    "esModuleInterop": true,
    // 대소문자가 다른경우에도 동일한 파일이름을 갖는 파일을 생성하지 못하도록한다
    "forceConsistentCasingInFileNames": false,
    // 모든 엄격한 타입체크 옵션 활성화
    "strict": true,
    // 선언 파일 유형 검사 스킵 (Tpyescript와 관련된 모듈의 타입정보)
    "skipLibCheck": true,
    // Typescript가 JSX해석 하는방식을 설정 
    "jsx": "react-jsx",
    // 기본출력(default export)이 없는 모듈로부터 기본 가져오기를 허용
    // "allowSyntheticDefaultImports": true,
    // 코드내에서 명시적으로 유형을 지정하지 않은 경우 any로 간주되어 오류발생시키도록 설정
    "noImplicitAny": true,
    // 모듈 분석방법설정 (컴파일러에게 올바르게 컴파일할 수 있도록 방법을제공)
    "moduleResolution": "node",
    // 컴파일러에게 어떤 라이브러리를 사용할것인지 알림
    // 기본적으로 dom, exnext를 사용한다. 지정을 할경우 해당 라이브러리 들만사용됨
    "lib": ["dom", "dom.iterable", "esnext"],
    // TypeScript가 JS 파일도 컴파일 할수 있도록 설정(JS와 TS를 같이 사용할경우 등)
    "allowJs": true,
    // TypeScript가 컴파일된 파일의 위치를 저장
    "outDir": "./TSdist",
    // .json 확장자를 가진 파일도 일반적인 모듈처럼 가져와서 사용할수 있도록 설정
    "resolveJsonModule": true,
    // 각 소스코드 파일을 독립적인 모듈로 취급하라고 지시(동일한 변수명 방지)
    "isolatedModules": true,
    // 모듈이 기본적으로 위치한 디렉토리 설정
    "baseUrl": "./",
    // baseUrl을 기준으로 절대경로를 지정함
    "paths": {
      "@components/*": ["src/components/*"]
    }
  },
  // 컴파일에 포함할 파일과 디렉토리를 지정, 명시하지않으면 루트 디렉토리의 TypeScript 파일이 컴파일 대상이됨
  "include": ["src"]
}

 

dev, pord 에서 설정한 절대경로는 tsconfig에서도 설정해주어야 했고 해당 설정들은 babel-loader로 적용되지않은 부분들을 해결하고 나서야 적용이 되는것 또한 확인했다. tsconfig도 보고 테스트 해볼것이 많은것 같다. 정상적으로 작동하는지, 무슨설정인지 알고 있어야 불필요한 설정을 빼고, 넣을수 있기때문에 이내용도 따로 다뤄봐야 겠다.

 

4.webpack.common.js

const path = require('path');
// HTML파일 생성을 단순화하며, 나만의 템플릿을 제공하도록 할수있다.
const HtmlWebpackPlugin = require('html-webpack-plugin');
// TypeScript 타입체크를 별도의 프로세스로 분리하여 속도를 높인다.
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');

module.exports = {
    // 웹팩의 출발지점을 설정(최상위 파일) - 다중 지정가능 ex) {main: 위치}
    entry: path.resolve(__dirname,'../src/index.tsx'),
    // webpack은 기본적으로 JS, JSON만 해석할수있다. 즉 다른 유형의 모듈을 처리하는 방법을 결정
    module:{
         /*
        rules: 모듈이 생성될때 요청과 일치하는 배열, 로더를 모듈에 적용시키거나 파서를 수정할수있다.
        1. test: 어떤파일을 변환할지 지정하는 속성
        2. use: 파일을 변환할때 어떤 로더를 사용해야하는지
        3. exclude: 외부모듈을 제외한다. 번들파일의 크기를줄이고, 빌드성능을 향상 즉, 필요한 모듈만 사용하기위해
        */
        rules:[
            {
                test: /\.(ts|tsx)$/,
                use: "ts-loader",
                exclude: /node_modules/,
            },
            {
                test: /\.js$/,
                use: "babel-loader",
                exclude: /node_modules/,
            },
        ]
    },
    // webpack 빌드 프로세스중 필요한 작업을 수행하는 객체(파일) 플러그인 적용
    plugins:[
        new HtmlWebpackPlugin({
            template: "public/index.html"
        }),
        new ForkTsCheckerWebpackPlugin(),
    ],
    // 모듈을 해석하는 방식을 변경할수있다.
    resolve:{
        // 확장자를 순서대로 해석하며 앞에서부터 해석하고 남은것은 해석하지 않는다.
        extensions: ['.js', '.ts', '.jsx', '.tsx', '.css', 'json'],
        // 절대경로 지정
        alias: {
            "@components": path.resolve("/src/components")
          },
    }
}

 

dev 와 prod에서 중복되는 코드를 common으로 옴겨주었다. 이후 webpack-merge를 통해 병합해주었고 devserver, build 또한 정상적으로 작동한다.

 

 

마치면서

해당글에서는 글내용보다는 코드의 내용이 더많다. 코드자체에 주석이 달려있으므로 글내용은 적었다. 하지만 저주석을 달기위해서는 많은 시간이 들어갔다. 그로인해 새로운 부분을 알고, 모르던 부분을 알게되며 헷갈렸던 개념들도 정리되기 도했다. 확실히 직접해보고 이것저것 삽질하는게 큰 도움이 되는것 같다. 취직을 위해 포토폴리오를 만드는게 아니였다면, 좀다 알아 보고싶은것도 너무 많았다. 처음이 어렵지 해보다보니 재밌기도 했다. webpack관련 내용은 시간이 난다면? 다시한번 명확하게 다뤄보고싶다. 

 

다른블로그로 보다가 esbuild-loader 을 알게되었는데. 해당 로더도 적용해서 시간도 비교해보고. 어떤이점이 있는지 테스트 해봐야겠다.

https://uitjja.tistory.com/manage/newpost/?type=post&returnURL=%2Fmanage%2Fposts%2F

 

Tistory

좀 아는 블로거들의 유용한 이야기

www.tistory.com

 

https://github.com/JangIkIk/WebPack-Test.git

 

GitHub - JangIkIk/WebPack-Test

Contribute to JangIkIk/WebPack-Test development by creating an account on GitHub.

github.com