2022. 8. 8. 15:30

[ASP.NET Core 6]  비주얼 스튜디오 + React 5 + Webpack + Babel + Sass + minimizer (개발, 배포 폴더)

 

'ASP.NET Core 6'기준으로 작업하긴 했지만

'React 5' 부분만 때서 다른 프로젝트에 그대로 사용할 수 있습니다.

 

하나의 포스팅으로 썼더니 너무 길어서 분할합니다.

 

연관글 영역

 

 

1. 'package.json' 작성

프로젝트에 'package.json'파일을 생성하고 내용을 아래와 같이 넣습니다.

 

{
    "name": "react-test",
    "version": "0.1.0",
    "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1",
        "start": "webpack-dev-server --mode development --hot",
        "build": "webpack --progress --profile",
        "development": "webpack serve --env development",
        "production": "webpack --progress --profile --mode production"
    },
    "devDependencies": {
        "@babel/core": "^7.18.9",
        "@babel/preset-env": "^7.18.9",
        "@babel/preset-react": "^7.18.6",
        "babel-loader": "^8.2.5",
        "file-loader": "^6.2.0",

        "css-loader": "^6.7.1",
        "css-minimizer-webpack-plugin": "^4.0.0",
        "sass": "^1.53.0",
        "postcss-loader": "^7.0.1",
        "sass-loader": "^13.0.2",
        "style-loader": "^3.3.1",
        "mini-css-extract-plugin": "^2.6.1",
        "html-loader": "^4.1.0",

        "html-webpack-plugin": "5.5.0",
        "webpack": "^5.73.0",
        "webpack-cli": "^4.10.0",
        "webpack-dev-server": "^4.9.3",
        "clean-webpack-plugin": "^4.0.0"
        "react-scripts": "^2.1.3",
    },
    "dependencies": {
        "@testing-library/jest-dom": "^5.16.4",
        "@testing-library/react": "^13.3.0",
        "@testing-library/user-event": "^13.5.0",
        "web-vitals": "^2.1.4",
        
        "react": "^18.2.0",
        "react-dom": "^18.2.0",
        
        "lodash": "^4.17.21",
        "axios": "^0.27.2",
        "html-react-parser":"^3.0.1"
    },
    "eslintConfig": {
        "extends": [
            "react-app",
            "react-app/jest"
        ]
    },
    "browserslist": {
        "production": [
            ">0.2%",
            "not dead",
            "not op_mini all"
        ],
        "development": [
            "last 1 chrome version",
            "last 1 firefox version",
            "last 1 safari version"
        ]
    }
}

 

1-1. 'scripts'

웹팩으로 빌드하거나 테스트하기위한 설정입니다.

여기에 설정한 내용이 '작업 러너 탐색기'에 UI로 표시됩니다.

 

"start": "webpack-dev-server --mode development --hot",

 - 개발 모드로 시작하는데 핫리로드를 활성화합니다.

"development": "webpack serve --env development",

 - 개발 모드로 실행합니다.

"build": "webpack --progress --profile",

 - 개발 모드로 빌드합니다.(실행은 안 함)

"production": "webpack --progress --profile --mode production" 

 - 배포 모드로 실행합니다.

 

 

1-2. 'devDependencies'

'devDependencies'는 개발에서 빌드까지를 담당하는 패키지를 넣는 곳입니다.

가끔 "'devDependencies'는 개발에 쓰고 'dependencies'는 배포에 쓴다"고 설명하는 경우가 있는데...

이거 엄밀하게 말하면 반만 맞는 설명이라고 할 수 있습니다.

 

'devDependencies'는 빌드가 끝날 때까지 사용하고(개발 빌드, 배포 빌드 포함)

테스트(development mode) 및 배포에는 'dependencies'만 사용됩니다.

쉽게 말하면 웹팩으로 번들링 하면 'dependencies'의 패키지는 포함되고 'devDependencies'의 패키지는 포함되지 않습니다.

 

배포 빌드던 개발 빌드던 빌드까지만 'devDependencies'가 영향을 주기 때문입니다.

 

 

 

1) 바벨(bable)

'ES2015'가 어쩌구 저쩌구......

라는데 결론은 브라우저 간 하위호환을 처리해주는 패키지입니다.

설정된 값에 따라 적절하게 하위호환을 위한 처리를 자동으로 해줍니다.

 

"@babel/core": "^7.18.9" : 바벨 코어
"@babel/preset-env": "^7.18.9" : 바벨 설정을 미리 해둔 패키지
"@babel/preset-react": "^7.18.6" : 리엑트 문법 변환을 위한 패키지
"babel-loader": "^8.2.5" : 바벨을 웹팩에 연결하기 위한 로더

 

 

2) 스타일 처리기, HTML 처리기

CSS, SASS 등을 처리하고 미니마이즈 해주는 패키지들입니다.

 

"css-loader": "^6.7.1" : 프로젝트에 사용된 css를 묶어주는 로더입니다.

 - css는 이름이 같으면 한쪽이 덮어지는데 이름이 겹치지 않도록 해줍니다.(마크업 언어에서 사용할 때만 처리됨)
"css-minimizer-webpack-plugin": "^4.0.0" : css를 미니마이즈 해주는 플러그인입니다.

 - 내부적으로 cssnano를 사용한다고 합니다.

"mini-css-extract-plugin": "^2.6.1" : CSS를 파일로 출력해 줍니다.

 - 이 프로젝트에서는 번들을 하므로 1개의 css 파일이 출력됩니다

"postcss-loader": "^7.0.1" : 스타일 처리가 끝나고 후처리할 때 사용하는 로더

 - webpack.config.js파일을 생성하여 세팅하면 그것에 따라서 추가로 후처리를 합니다.

 - 이 프로젝트에서는 후처리가 필요 없어서 로더만 넣어두고 설정은 넣지 않았습니다.
"sass": "^1.53.0" : sass 처리기
"sass-loader": "^13.0.2" : 웹팩에 SASS처리를 위한 로더

"html-loader": "^4.1.0" : 웹팩에 HTML처리를 위한 로더

 - HTML파일을 문자열로 읽어줍니다.

 - 필요에 따라 미니마이징해줍니다.

 

 

3) 웹팩

"webpack": "^5.73.0" : 웹팩 코어
"webpack-cli": "^4.10.0" : 커맨드 라인 인터페이스(Command Line Interface)를 사용하기 위한 패키지
"webpack-dev-server": "^4.9.3" : 테스트용 웹서버 패키지

 

 

4) 빌드 후처리기

빌드의 끝자락에 동작하는 패키지입니다.

 

"html-webpack-plugin": "5.5.0" : 사용된 파일을 html의 임포트 코드로 넣어주는 플러그인입니다.

 - 여기서는 번들링 된 파일만 생성되므로 번들링 된 파일을 연결해줍니다.

"file-loader": "^6.2.0" : 이미지 파일과 같은 별도 파일 중 프로젝트에 사용된 파일만 출력 폴더에 복사해주는 패키지입니다.

"clean-webpack-plugin": "^4.0.0" : 빌드 시 웹팩에서 생성한 필요 없는 파일들을 자동으로 제거해줍니다.


5) 리액트 도구

"react-scripts": "^2.1.3" : 리액트의 빌드 처리를 도와주는 기능들이 들어 있다고 합니다.

 - 빌드에만 사용하므로 이곳에 넣어야 하는데 'dependencies'에 넣는 예제도 많습니다.

 

 

1-3. 'dependencies'

배포에 들어가는 패키지들입니다.

 

1) 리액트 테스트 도구

리액트 프로젝트 생성에 사용하는 CRA(Create React App)로 생성할 때 들어있는 테스트 도구들입니다.

 

"@testing-library/jest-dom": "^5.16.4"
"@testing-library/react": "^13.3.0"
"@testing-library/user-event": "^13.5.0"

"web-vitals": "^2.1.4" : 구글에서 제공하는 각종 측정을 위한 도구입니다.

 

 

 

2) 리액트 패키지

"react": "^18.2.0"
"react-dom": "^18.2.0"

 

2-1) 리액트용 유틸

"html-react-parser":"^3.0.1" : html 문자열을 React 개체로 변환해 주는 파서입니다.

 - 리액트에서 직접 HTML을 넣으려면 'SetInnerHTML'나 'dangerouslySetInnerHTML'를 이용해야 하는데 이것을 자동화해줍니다.

 

 

3) 기타 유틸

"lodash": "^4.17.21" : 자바스크립트에서 배열을 쉽게 다룰 수 있게 해주는 라이브러리입니다.

"axios": "^0.27.2" : Ajax 처리를 하는 라이브러리입니다.

 - 제이쿼리(jquery) Ajax와 비슷한 라이브러리입니다.

 

 

 

1-4. 'eslintConfig'

'ESLint'는 자바스크립트 문법 검사기입니다.

IDE의 설정에 따라 오류나 경고를 미리 표시해줍니다.

 

자세한 설정은 검색해서 수정하시면 됩니다.

"extends": [
    "react-app",
    "react-app/jest"
]

 

 

1-5. 'browserslist '

어떤 조건의 브라우저까지 지원할지를 설정합니다.

이 조건에 맞게 웹팩이 빌드한다고 합니다

 

1) production : 배포 버전일 때

">0.5%" : 전 세계 점유율 0.5% 이상인 브라우저만

"not dead" : 지원중단이 되지 않은 브라우저만

"not op_mini all" : 오페라 미니는 사용 안 함.

 

 

2) development : 개발 버전일 때

"last 1 chrome version" : 크롬 최신 버전만

"last 1 firefox version" : 파이어폭스 최신 버전만

"last 1 safari version" : 사파리 최신 버전만

 

 

 

2. 'webpack.config.js'

루트 폴더에 'webpack.config.js'파일을 생성합니다.

const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");

//소스 위치
const RootPath = path.resolve(__dirname);
const SrcPath = path.resolve(RootPath, "src");

//웹서버가 사용하는 폴더 이름
const WwwRoot = "wwwroot";
//웹서버가 사용하는 폴더 위치
const WwwRootPath = path.resolve(__dirname, WwwRoot);
//리액트 템플릿 위치
const React_IndexHtmlPath = path.resolve(SrcPath, "index.html");
//결과물 출력 폴더 이름
let OutputFolder = "development";
//결과물 출력 폴더 이름 - 이미지 폴더
const OutputFolder_Images = "images";
//결과물 출력 위치
let OutputPath = path.resolve(WwwRootPath, OutputFolder);
//결과물 출력 위치 - 상대 주소
let OutputPath_relative = path.resolve("/", OutputFolder);


module.exports = (env, argv) =>
{
    //릴리즈(프로덕션)인지 여부
    const EnvPrductionIs = argv.mode === "production";
    if (true === EnvPrductionIs)
    {
        //릴리즈 출력 폴더 변경
        OutputFolder = "production";
        OutputPath = path.resolve(WwwRootPath, OutputFolder);
        OutputPath_relative = path.resolve("/", OutputFolder);
    }

    return {
        /** 서비스 모드 */
        mode: EnvPrductionIs ? "production" : "development",
        devtool: "eval",
        //devtool: "inline-source-map",
        resolve: {
            extensions: [".js", ".jsx"]
        },
        entry: { // 합쳐질 파일 요소들 입력
            app: [path.resolve(SrcPath, "index.js")],
        },
        output: {// 최종적으로 만들어질 js
            /** 빌드 위치 */
            path: OutputPath,
            /** 웹팩 빌드 후 최종적으로 만들어질 파일 */
            filename: "app.js"
        },
        module: {
            rules: [
                {//소스 파일
                    test: /\.(js|jsx)$/,
                    exclude: /node_module/,
                    use:
                        [
                            { loader: "babel-loader" },
                        ]
                },
                {//스타일 파일
                    test: /\.(sa|sc|c)ss$/i,
                    exclude: /node_module/,
                    use:
                        [
                            {
                                //개발 버전에서는 style-loader 사용
                                loader: MiniCssExtractPlugin.loader,
                                options: { esModule: false }
                            },
                            { loader: 'css-loader' },
                            { loader: 'sass-loader' },
                            { loader: 'postcss-loader' },
                        ]
                },
                {//html 파일
                    test: /\.html$/i,
                    loader: "html-loader",
                },
                {//이미지 파일
                    rules: [
                        {
                            test: /\.(png|jpg|gif|svg|webp)$/,
                            loader: "file-loader",
                            options: {
                                outputPath: OutputFolder_Images,
                                name: "[name].[ext]?[hash]",
                            }
                        },
                    ],
                }
            ]
        },
        optimization: {
            minimizer: [
                new CssMinimizerPlugin(),
            ],
        },
        plugins: [
            // 빌드한 결과물(예>번들파일)을 HTML에 삽입해주는 플러그인
            new HtmlWebpackPlugin({ template: React_IndexHtmlPath }),
            // 출력폴더를 비워주는 플러그인
            new CleanWebpackPlugin({
                cleanOnceBeforeBuildPatterns: [
                    '**/*',
                    "!robots.txt",
                    "!Upload"
                ]
            }),
            // 별도로 css 파일을 만들어서 빌드하는 플러그인
            new MiniCssExtractPlugin({
                filename: "app.css"
            })
        ],
        devServer: {
            /** 서비스 포트 */
            port: "9500",
            /** 출력파일의 위치 */
            static: [path.resolve("./", WwwRoot)],
            /** 브라우저 열지 여부 */
            open: true,
            /** 핫리로드 사용여부 */
            hot: true,
            /** 라이브 리로드 사용여부 */
            liveReload: true
        },
    };
}

 

2-1. 환경 변수 등록

플러그인을 비롯한 각종 설정을 좀 더 편하게 하려면 환경 변수를 등록해야 합니다.

const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");

 

const path = require("path");

 - 경로 처리 일관성을 위한 도구를 사용하기 위한 선언입니다.

 - 운영체제별로 경로 처리 같은 다른 것들을 처리해줍니다.

 

 

2-2. 각종 경로

각 경로는 그때그때 문자열로 넣어도 되지만

그렇게 되면 관리가 힘들어집니다.

그래서 이렇게 사용할 경로들을 미리 만들어 두는 것이 좋습니다.

//소스 위치
const RootPath = path.resolve(__dirname);
const SrcPath = path.resolve(RootPath, "src");

//웹서버가 사용하는 폴더 이름
const WwwRoot = "wwwroot";
//웹서버가 사용하는 폴더 위치
const WwwRootPath = path.resolve(__dirname, WwwRoot);
//리액트 템플릿 위치
const React_IndexHtmlPath = path.resolve(SrcPath, "index.html");
//결과물 출력 폴더 이름
let OutputFolder = "development";
//결과물 출력 폴더 이름 - 이미지 폴더
const OutputFolder_Images = "images";
//결과물 출력 위치
let OutputPath = path.resolve(WwwRootPath, OutputFolder);
//결과물 출력 위치 - 상대 주소
let OutputPath_relative = path.resolve("/", OutputFolder);

 

 

배포(프로덕션, production) 모드시 경로 변경

배포용 폴더를 따로 두려면 배포 모드일 때 경로를 변경해야 합니다.

//릴리즈(프로덕션)인지 여부
const EnvPrductionIs = argv.mode === "production";
if (true === EnvPrductionIs)
{
    //릴리즈 출력 폴더 변경
    OutputFolder = "production";
    OutputPath = path.resolve(WwwRootPath, OutputFolder);
    OutputPath_relative = path.resolve("/", OutputFolder);
}

 

출력물 폴더는 빌드 때마다 삭제되므로

웹 루트 폴더를 지정하면 임의로 저장된 파일까지 지워버리는 일이 생깁니다.

그래서 별도의 하위 폴더를 만들어 출력하는 것입니다.

 

웹서버에서는 하위 폴더를 시작 폴더로 지정하여 사용하면

웹 루트 폴더는 원하는 대로 사용하면서(대표적으로 robot.txt) 웹은 웹대로 돌게 되죠.

(물론 예외 처리 하면 됩니다 ㅎ)

 

 

2-3. 모듈 설정

조건에 맞는 파일들을 적절한 로더로 전달하기 위한 설정입니다.

 

test : 대상 파일

use : 연결할 로더

 

 

2-4. 플러그인 설정

패키지에서 불러온 플러그인 중 사용할 플러그인을 설정해줍니다.

 

CleanWebpackPlugin : 출력 폴더를 비워주는 플러그인입니다.

패턴을 설정하여 지우지 말아야 할 파일이나 폴더를 지정할 수 있습니다.

// 출력폴더를 비워주는 플러그인
new CleanWebpackPlugin({
    cleanOnceBeforeBuildPatterns: [
        '**/*',
        "!robots.txt",
        "!Upload"
    ]
}),

이 설정은

모든 파일을 지우되

'robots.txt'파일은 제외하고

'Upload'폴더도 제외하라는 설정입니다.

 

 

 

2-5. 테스트 서버 설정

프론트엔드만 테스트할 수 있는 테스트 서버입니다.

백엔드는 동작하지 않습니다.

 

'ASP.NET Core 6'같은 프로젝트는 자체적으로 테스트 웹서버가 있으므로

필요 없다면 설정하지 않아도 됩니다.

 

 

3. '.babelrc' 파일 생성

루트 폴더에 '.babelrc'파일을 생성합니다.

{
    /*
        a preset is a set of plugins used to support particular language features.
        The two presets Babel uses by default: es2015, react
    */
    "presets": [
        "@babel/preset-env", //compiling ES2015+ syntax
        "@babel/preset-react" //for react
    ],
    /*
        Babel's code transformations are enabled by applying plugins (or presets) to your configuration file.
    */
    "plugins": [
        //"@babel/plugin-transform-runtime"
    ]
}

 

4. 패키지 복원

이제 패키지 복원을 해서 패키지를 다운받아야 합니다.

아래 방법중 한 가지를 이용하여 복원하면 됩니다.

 

작업 러너 탐색기

'작업 러너 탐색기'에서 프로젝트를 선택하고

'install'에서 오른쪽 클릭 -> 실행해줍니다.

(참고 : [ASP.NET Core 6] ASP.NET에 NPM 세팅하기 (feat. Task Runner))

 

패키지 복원

솔루션 탐색기 > package.json에서 오른쪽 클릭 > 패키지 복원

 

 

터미널에서 복원

● 비주얼 스튜디오의 경우

솔루션 탐색기 > 해당 프로젝트에서 오른쪽 클릭 > 터미널에서 열기

 

● VS Code의 경우

터미널 > 새 터미널

 

터미널에서 아래 명령어를 날려 줍니다.

npm install

 

 

취약점 해결하기

npm에서 취약점은 일상입니다....

(참고 : [React 5] 'create-react-app'으로 생성한 프로젝트의 오류와 경고 제거하기)

크리티컬이 3개나...

 

그래도 크리티컬이 남아있으면 찝찝하니

아래 두 명령 중 하나를 사용하여 잡아줍시다.

//마이너 버전까지만 업데이트
npm audit fix

//메이저 버전까지 업데이트
npm audit fix --force

 

역시 다 잡힐리가 없지...

 

 

5. 'src' 폴더 생성

이제 본격적으로 소스 코드를 넣어야 합니다.

이 포스팅에서는 소스 코드를 분석하지 않습니다.

 

깃허브에 있는 파일들을 다운받아 넣어주세요.

참고 : github - dang-gun/AspDotNetSamples/ReactTest/src

src.7z
0.00MB

 

 

6. 프론트 엔드 빌드 및 테스트

이제 빌드하고 테스트해봅시다.

 

● 작업 러너 탐색기

'build에서 오른쪽 클릭을 하고 실행하면 빌드가 진행됩니다.

 

● 터미널

아래 명령을 사용하여 빌드할 수 있습니다.

npm run build

 

 

오류가 없으면 'wwwroot'에 'development'폴더가 생성되고 출력물이 들어갑니다.

 

'development'를 실행하거나 터미널에 'npm run development' 명령을 사용하여

테스트해봅시다.

 

잘되네요.

 

 

마무리

완성된 프로젝트 : github - dang-gun/AspDotNetSamples/ReactTest/

 

가급적 깔끔하게 하려고 다른 패키지를 최대한 안 쓰려고 했는데.....

그럴리가 있나 ㅎㅎㅎㅎㅎ