제어 유지: Webpack 및 React 가이드, Pt. 1

게시 됨: 2022-03-11

새로운 React 프로젝트를 시작할 때 선택할 수 있는 템플릿이 많이 있습니다: Create React App, react-boilerplate , React Starter Kit 등.

수천 명의 개발자가 채택한 이러한 템플릿은 대규모 애플리케이션 개발을 지원할 수 있습니다. 그러나 그들은 개발자 경험을 남겨두고 다양한 기본값으로 묶인 번들 출력을 제공하므로 이상적이지 않을 수 있습니다.

빌드 프로세스에 대한 더 큰 제어를 유지하려면 사용자 지정 Webpack 구성에 투자하도록 선택할 수 있습니다. 이 Webpack 튜토리얼에서 배우게 될 것처럼 이 작업은 그다지 복잡하지 않으며 지식은 다른 사람의 구성 문제를 해결할 때 유용할 수도 있습니다.

웹팩: 시작하기

오늘날 우리가 자바스크립트를 작성하는 방식은 브라우저가 실행할 수 있는 코드와 다릅니다. 우리는 최신 브라우저에서 아직 지원되지 않는 다른 유형의 리소스, 변환된 언어 및 실험 기능에 자주 의존합니다. Webpack은 이 격차를 해소하고 개발자 경험과 관련하여 비용 부담 없이 크로스 브라우저 호환 코드를 생성할 수 있는 JavaScript용 모듈 번들러입니다.

시작하기 전에 이 Webpack 튜토리얼에 제공된 모든 코드는 GitHub에서 완전한 Webpack/React 예제 구성 파일의 형태로도 사용할 수 있다는 점을 염두에 두어야 합니다. 거기에서 자유롭게 참조하고 질문이 있는 경우 이 문서로 다시 오십시오.

기본 구성

Legato(버전 4)부터 Webpack을 실행하기 위해 구성이 필요하지 않습니다. 빌드 모드를 선택하면 대상 환경에 더 적합한 일련의 기본값이 적용됩니다. 이 기사의 정신에서 우리는 이러한 기본값을 무시하고 각 대상 환경에 대해 합리적인 구성을 직접 구현할 것입니다.

먼저 webpackwebpack-cli 를 설치해야 합니다.

 npm install -D webpack webpack-cli

그런 다음 webpack.config.js 를 다음 옵션이 포함된 구성으로 채워야 합니다.

  • devtool : 개발 모드에서 소스 맵 생성을 활성화합니다.
  • entry : React 애플리케이션의 기본 파일입니다.
  • output.path : 출력 파일을 저장할 루트 디렉터리입니다.
  • output.filename : 생성된 파일에 사용할 파일 이름 패턴입니다.
  • output.publicPath : 웹 서버에서 파일이 배포될 루트 디렉터리의 경로입니다.
 const path = require("path"); module.exports = function(_env, argv) { const isProduction = argv.mode === "production"; const isDevelopment = !isProduction; return { devtool: isDevelopment && "cheap-module-source-map", entry: "./src/index.js", output: { path: path.resolve(__dirname, "dist"), filename: "assets/js/[name].[contenthash:8].js", publicPath: "/" } }; };

위의 구성은 일반 JavaScript 파일에 대해 잘 작동합니다. 그러나 Webpack 및 React를 사용할 때 사용자에게 코드를 제공하기 전에 추가 변환을 수행해야 합니다. 다음 섹션에서는 Babel을 사용하여 Webpack이 JavaScript 파일을 로드하는 방식을 변경할 것입니다.

JS 로더

Babel은 코드 변환을 위한 많은 플러그인이 있는 JavaScript 컴파일러입니다. 이 섹션에서는 Webpack 구성에 로더로 도입하고 최신 JavaScript 코드를 일반 브라우저에서 이해할 수 있는 코드로 변환하도록 구성합니다.

먼저 babel-loader@babel/core 를 설치해야 합니다.

 npm install -D @babel/core babel-loader

그런 다음 Webpack 구성에 module 섹션을 추가하여 babel-loader 가 JavaScript 파일을 로드하도록 합니다.

 @@ -11,6 +11,25 @@ module.exports = function(_env, argv) { path: path.resolve(__dirname, "dist"), filename: "assets/js/[name].[contenthash:8].js", publicPath: "/" + }, + module: { + rules: [ + { + test: /\.jsx?$/, + exclude: /node_modules/, + use: { + loader: "babel-loader", + options: { + cacheDirectory: true, + cacheCompression: false, + envName: isProduction ? "production" : "development" + } + } + } + ] + }, + resolve: { + extensions: [".js", ".jsx"] } }; };

우리는 babel.config.js 라는 별도의 설정 파일을 사용하여 Babel을 설정할 것입니다. 다음 기능을 사용합니다.

  • @babel/preset-env : 최신 JavaScript 기능을 이전 버전과 호환되는 코드로 변환합니다.
  • @babel/preset-react : JSX 구문을 일반 바닐라 JavaScript 함수 호출로 변환합니다.
  • @babel/plugin-transform-runtime : Babel 도우미를 공유 모듈로 추출하여 코드 중복을 줄입니다.
  • @babel/plugin-syntax-dynamic-import : 기본 Promise 지원이 없는 브라우저에서 동적 import() 구문을 활성화합니다.
  • @babel/plugin-proposal-class-properties : 클래스 기반 React 구성 요소를 작성하기 위해 공개 인스턴스 필드 구문 제안에 대한 지원을 활성화합니다.

또한 몇 가지 React 관련 프로덕션 최적화를 활성화할 것입니다.

  • babel-plugin-transform-react-remove-prop-types 는 프로덕션 코드에서 불필요한 소품 유형을 제거합니다.
  • @babel/plugin-transform-react-inline-elements 는 컴파일하는 동안 React.createElement 를 평가하고 결과를 인라인합니다.
  • @babel/plugin-transform-react-constant-elements 는 정적 React 요소를 상수로 추출합니다.

아래 명령은 필요한 모든 종속성을 설치합니다.

 npm install -D @babel/preset-env @babel/preset-react @babel/runtime @babel/plugin-transform-runtime @babel/plugin-syntax-dynamic-import @babel/plugin-proposal-class-properties babel-plugin-transform-react-remove-prop-types @babel/plugin-transform-react-inline-elements @babel/plugin-transform-react-constant-elements

그런 다음 babel.config.js 를 다음 설정으로 채웁니다.

 module.exports = { presets: [ [ "@babel/preset-env", { modules: false } ], "@babel/preset-react" ], plugins: [ "@babel/plugin-transform-runtime", "@babel/plugin-syntax-dynamic-import", "@babel/plugin-proposal-class-properties" ], env: { production: { only: ["src"], plugins: [ [ "transform-react-remove-prop-types", { removeImport: true } ], "@babel/plugin-transform-react-inline-elements", "@babel/plugin-transform-react-constant-elements" ] } } };

이 구성을 통해 모든 관련 브라우저와 호환되는 방식으로 최신 JavaScript를 작성할 수 있습니다. React 애플리케이션에 필요할 수 있는 다른 유형의 리소스가 있으며 이에 대해서는 다음 섹션에서 다룰 것입니다.

CSS 로더

React 애플리케이션을 스타일링할 때 최소한 일반 CSS 파일을 포함할 수 있어야 합니다. 다음 로더를 사용하여 Webpack에서 이 작업을 수행할 것입니다.

  • css-loader : CSS 파일을 구문 분석하여 이미지, 글꼴 및 추가 스타일 가져오기와 같은 외부 리소스를 확인합니다.
  • style-loader : 개발 중에 로드된 스타일을 런타임에 문서에 삽입합니다.
  • mini-css-extract-plugin : 브라우저 캐싱을 활용하기 위해 프로덕션 사용을 위해 로드된 스타일을 별도의 파일로 추출합니다.

위의 CSS 로더를 설치해 보겠습니다.

 npm install -D css-loader style-loader mini-css-extract-plugin

그런 다음 Webpack 구성의 module.rules 섹션에 새 규칙을 추가합니다.

 @@ -1,4 +1,5 @@ const path = require("path"); +const MiniCssExtractPlugin = require("mini-css-extract-plugin"); module.exports = function(_env, argv) { const isProduction = argv.mode === "production"; @@ -25,6 +26,13 @@ module.exports = function(_env, argv) { envName: isProduction ? "production" : "development" } } + }, + { + test: /\.css$/, + use: [ + isProduction ? MiniCssExtractPlugin.loader : "style-loader", + "css-loader" + ] } ] },

또한 plugins 섹션에 MiniCssExtractPlugin 을 추가합니다. 이 섹션은 프로덕션 모드에서만 활성화합니다.

 @@ -38,6 +38,13 @@ module.exports = function(_env, argv) { }, resolve: { extensions: [".js", ".jsx"] - } + }, + plugins: [ + isProduction && + new MiniCssExtractPlugin({ + filename: "assets/css/[name].[contenthash:8].css", + chunkFilename: "assets/css/[name].[contenthash:8].chunk.css" + }) + ].filter(Boolean) }; };

이 구성은 일반 CSS 파일에서 작동하며 다음 기사에서 논의할 Sass 및 PostCSS와 같은 다양한 CSS 프로세서와 함께 작동하도록 확장할 수 있습니다.

이미지 로더

Webpack은 또한 이미지, 비디오 및 기타 바이너리 파일과 같은 정적 리소스를 로드하는 데 사용할 수 있습니다. 이러한 유형의 파일을 처리하는 가장 일반적인 방법은 file-loader 또는 url-loader 를 사용하는 것입니다. 이는 소비자에게 필요한 리소스에 대한 URL 참조를 제공합니다.

이 섹션에서는 일반적인 이미지 형식을 처리하기 위해 url-loader 를 추가합니다. url-loaderfile-loader 와 다른 점은 원본 파일의 크기가 주어진 임계값보다 작은 경우 URL에 전체 파일을 base64로 인코딩된 내용으로 포함하므로 추가 요청이 필요 없다는 것입니다.

먼저 url-loader 를 설치합니다.

 npm install -D url-loader

그런 다음 Webpack 구성의 module.rules 섹션에 새 규칙을 추가합니다.

 @@ -34,6 +34,16 @@ module.exports = function(_env, argv) { isProduction ? MiniCssExtractPlugin.loader : "style-loader", "css-loader" ] + }, + { + test: /\.(png|jpg|gif)$/i, + use: { + loader: "url-loader", + options: { + limit: 8192, + name: "static/media/[name].[hash:8].[ext]" + } + } } ] },

SVG

SVG 이미지의 경우 가져온 파일을 React 구성 요소로 변환하는 @svgr/webpack 로더를 사용할 것입니다.

@svgr/webpack 을 설치합니다.

 npm install -D @svgr/webpack

그런 다음 Webpack 구성의 module.rules 섹션에 새 규칙을 추가합니다.

 @@ -44,6 +44,10 @@ module.exports = function(_env, argv) { name: "static/media/[name].[hash:8].[ext]" } } + }, + { + test: /\.svg$/, + use: ["@svgr/webpack"] } ] },

SVG 이미지는 React 구성 요소로 편리할 수 있으며 @svgr/webpack 은 SVGO를 사용하여 최적화를 수행합니다.

참고: 특정 애니메이션이나 마우스 오버 효과의 경우 JavaScript를 사용하여 SVG를 조작해야 할 수도 있습니다. 다행히 @svgr/webpack 은 기본적으로 SVG 콘텐츠를 JavaScript 번들에 포함하므로 이에 필요한 보안 제한을 우회할 수 있습니다.

파일 로더

다른 종류의 파일을 참조해야 하는 경우 일반 file-loader 가 작업을 수행합니다. url-loader 와 유사하게 작동하며 이를 필요로 하는 코드에 자산 URL을 제공하지만 최적화하려는 시도는 하지 않습니다.

항상 그렇듯이 먼저 Node.js 모듈을 설치합니다. 이 경우 file-loader :

 npm install -D file-loader

그런 다음 Webpack 구성의 module.rules 섹션에 새 규칙을 추가합니다. 예를 들어:

 @@ -48,6 +48,13 @@ module.exports = function(_env, argv) { { test: /\.svg$/, use: ["@svgr/webpack"] + }, + { + test: /\.(eot|otf|ttf|woff|woff2)$/, + loader: require.resolve("file-loader"), + options: { + name: "static/media/[name].[hash:8].[ext]" + } } ] },

여기에 CSS 파일에서 참조할 수 있는 글꼴 로드를 위한 file-loader 가 추가되었습니다. 이 예제를 확장하여 필요한 다른 종류의 파일을 로드할 수 있습니다.

환경 플러그인

Webpack의 DefinePlugin() 을 사용하여 빌드 환경의 환경 변수를 애플리케이션 코드로 노출할 수 있습니다. 예를 들어:

 @@ -1,5 +1,6 @@ const path = require("path"); const MiniCssExtractPlugin = require("mini-css-extract-plugin"); +const webpack = require("webpack"); module.exports = function(_env, argv) { const isProduction = argv.mode === "production"; @@ -65,7 +66,12 @@ module.exports = function(_env, argv) { new MiniCssExtractPlugin({ filename: "assets/css/[name].[contenthash:8].css", chunkFilename: "assets/css/[name].[contenthash:8].chunk.css" - }) + }), + new webpack.DefinePlugin({ + "process.env.NODE_ENV": JSON.stringify( + isProduction ? "production" : "development" + ) + }) ].filter(Boolean) }; };

여기에서 process.env.NODE_ENV 를 빌드 모드를 나타내는 문자열로 대체했습니다: "development" 또는 "production" .

HTML 플러그인

index.html 파일이 없으면 JavaScript 번들은 아무 소용이 없으며 아무도 찾을 수 없습니다. 이 섹션에서는 HTML 파일을 생성하기 위해 html-webpack-plugin 을 소개합니다.

html-webpack-plugin 을 설치합니다.

 npm install -D html-webpack-plugin

그런 다음 Webpack 구성의 plugins 섹션에 html-webpack-plugin 을 추가합니다.

 @@ -1,6 +1,7 @@ const path = require("path"); const MiniCssExtractPlugin = require("mini-css-extract-plugin"); const webpack = require("webpack"); +const HtmlWebpackPlugin = require("html-webpack-plugin"); module.exports = function(_env, argv) { const isProduction = argv.mode === "production"; @@ -71,6 +72,10 @@ module.exports = function(_env, argv) { "process.env.NODE_ENV": JSON.stringify( isProduction ? "production" : "development" ) + }), + new HtmlWebpackPlugin({ + template: path.resolve(__dirname, "public/index.html"), + inject: true }) ].filter(Boolean) };

생성된 public/index.html 파일은 번들을 로드하고 애플리케이션을 부트스트랩합니다.

최적화

빌드 프로세스에서 사용할 수 있는 몇 가지 최적화 기술이 있습니다. 기능 면에서 비용 없이 번들의 크기를 줄일 수 있는 프로세스인 코드 축소부터 시작하겠습니다. 코드를 최소화하기 위해 JavaScript 코드용 terser-webpack-pluginoptimize-css-assets-webpack-plugin 두 가지 플러그인을 사용합니다.

설치해 보겠습니다.

 npm install -D terser-webpack-plugin optimize-css-assets-webpack-plugin

그런 다음 구성에 optimization 섹션을 추가합니다.

 @@ -2,6 +2,8 @@ const path = require("path"); const MiniCssExtractPlugin = require("mini-css-extract-plugin"); const webpack = require("webpack"); const HtmlWebpackPlugin = require("html-webpack-plugin"); +const TerserWebpackPlugin = require("terser-webpack-plugin"); +const OptimizeCssAssetsPlugin = require("optimize-css-assets-webpack-plugin"); module.exports = function(_env, argv) { const isProduction = argv.mode === "production"; @@ -75,6 +77,27 @@ module.exports = function(_env, argv) { isProduction ? "production" : "development" ) }) - ].filter(Boolean) + ].filter(Boolean), + optimization: { + minimize: isProduction, + minimizer: [ + new TerserWebpackPlugin({ + terserOptions: { + compress: { + comparisons: false + }, + mangle: { + safari10: true + }, + output: { + comments: false, + ascii_only: true + }, + warnings: false + } + }), + new OptimizeCssAssetsPlugin() + ] + } }; };

위의 설정은 모든 최신 브라우저와의 코드 호환성을 보장합니다.

코드 분할

코드 분할은 애플리케이션의 성능을 개선하는 데 사용할 수 있는 또 다른 기술입니다. 코드 분할은 두 가지 다른 접근 방식을 참조할 수 있습니다.

  1. 동적 import() 문을 사용하여 번들 크기의 상당 부분을 구성하는 애플리케이션 부분을 추출하고 요청 시 로드할 수 있습니다.
  2. 브라우저 캐싱을 활용하고 반복 방문자의 성능을 개선하기 위해 덜 자주 변경되는 코드를 추출할 수 있습니다.

타사 종속성과 공통 청크를 별도의 파일로 추출하기 위한 설정으로 Webpack 구성의 optimization.splitChunks .splitChunks 섹션을 채웁니다.

 @@ -99,7 +99,29 @@ module.exports = function(_env, argv) { sourceMap: true }), new OptimizeCssAssetsPlugin() - ] + ], + splitChunks: { + chunks: "all", + minSize: 0, + maxInitialRequests: 20, + maxAsyncRequests: 20, + cacheGroups: { + vendors: { + test: /[\\/]node_modules[\\/]/, + name(module, chunks, cacheGroupKey) { + const packageName = module.context.match( + /[\\/]node_modules[\\/](.*?)([\\/]|$)/ + )[1]; + return `${cacheGroupKey}.${packageName.replace("@", "")}`; + } + }, + common: { + minChunks: 2, + priority: -10 + } + } + }, + runtimeChunk: "single" } }; };

여기에서 사용한 옵션을 자세히 살펴보겠습니다.

  • chunks: "all" : 기본적으로 공통 청크 추출은 동적 import() 로 로드된 모듈에만 영향을 줍니다. 이 설정은 진입점 로딩에 대한 최적화도 가능하게 합니다.
  • minSize: 0 : 기본적으로 특정 크기 임계값 이상의 청크만 추출 대상이 됩니다. 이 설정을 사용하면 크기에 관계없이 모든 공통 코드에 대해 최적화할 수 있습니다.
  • maxInitialRequests: 20maxAsyncChunks: 20 : 이 설정은 진입점 가져오기 및 분할점 가져오기 각각에 대해 병렬로 로드할 수 있는 소스 파일의 최대 수를 늘립니다.

또한 다음과 같은 cacheGroups 구성을 지정합니다.

  • vendors : 타사 모듈에 대한 추출을 구성합니다.
    • test: /[\\/]node_modules[\\/]/ : 타사 종속성을 일치시키기 위한 파일 이름 패턴입니다.
    • name(module, chunks, cacheGroupKey) : 동일한 모듈의 개별 청크에 공통 이름을 부여하여 함께 그룹화합니다.
  • common : 애플리케이션 코드에서 공통 청크 추출을 구성합니다.
    • minChunks: 2 : 적어도 두 개의 모듈에서 참조되는 경우 청크는 공통된 것으로 간주됩니다.
    • priority: -10 : vendors 캐시 그룹에 대한 청크가 먼저 고려되도록 common 캐시 그룹에 음수 우선 순위를 할당합니다.

또한 runtimeChunk: "single" 을 지정하여 여러 진입점 간에 공유할 수 있는 단일 청크로 Webpack 런타임 코드를 추출합니다.

개발 서버

지금까지 우리는 애플리케이션의 프로덕션 빌드를 만들고 최적화하는 데 중점을 두었지만 Webpack에는 라이브 다시 로드 및 오류 보고 기능이 있는 자체 웹 서버도 있어 개발 프로세스에 도움이 됩니다. webpack-dev-server 라고 하며 별도로 설치해야 합니다.

 npm install -D webpack-dev-server

이 스니펫에서는 Webpack 구성에 devServer 섹션을 소개합니다.

 @@ -120,6 +120,12 @@ module.exports = function(_env, argv) { } }, runtimeChunk: "single" + }, + devServer: { + compress: true, + historyApiFallback: true, + open: true, + overlay: true } }; };

여기에서는 다음 옵션을 사용했습니다.

  • compress: true : 더 빠른 재로드를 위해 자산 압축을 활성화합니다.
  • historyApiFallback: true : 히스토리 기반 라우팅을 위해 index.html 로의 폴백을 활성화합니다.
  • open: true : 개발 서버를 시작한 후 브라우저를 엽니다.
  • overlay: true : 브라우저 창에 Webpack 오류를 표시합니다.

API 요청을 백엔드 서버로 전달하도록 프록시 설정을 구성해야 할 수도 있습니다.

Webpack 및 React: 성능 최적화 및 준비 완료!

Webpack으로 다양한 리소스 유형을 로드하는 방법, 개발 환경에서 Webpack을 사용하는 방법 및 프로덕션 빌드를 최적화하기 위한 여러 기술을 방금 배웠습니다. 필요한 경우 언제든지 전체 구성 파일을 검토하여 자신의 React/Webpack 설정에 대한 영감을 얻을 수 있습니다. 이러한 기술을 연마하는 것은 React 개발 서비스를 제공하는 모든 사람에게 표준 요금입니다.

이 시리즈의 다음 부분에서는 TypeScript 사용, CSS 전처리기, 서버 측 렌더링 및 ServiceWorkers와 관련된 고급 최적화 기술을 포함하여 보다 구체적인 사용 사례에 대한 지침으로 이 구성을 확장할 것입니다. React 애플리케이션을 프로덕션으로 가져오기 위해 Webpack에 대해 알아야 할 모든 것을 배우려면 계속 지켜봐 주십시오.