회사에서 공부해왔던 프론트엔드와는 무관한 ACS 시뮬레이션 업무를 해오며,
본의 아니게 코드를 놓은지 거의 3개월째이다.
경각심을 느껴, 제로초의 web-game 강의로 다시 리액트 감을 좀 끌어올리는 겸
공부한 내용을 정리하려한다.
툴체인에 대한 이해가 많이 부족하다고 생각하는데,
툴체인
- 패키지 매니저 (package manager)
- 번들러 (bundler)
- 컴파일러 (compiler)
- 포멧터 (formatter)
- 린터 (linter)
- 테스트 러너 (test runner)
- 미니마이저 (minimizer)
- 서버 (server)
그중 첫번째로, 번들러중 하나인 Webpack에 대해 정리해보려 한다.
1. Webpack이란?
현대의 웹사이트는 수많은 HTML, CSS, JavaScript, 이미지들이 모여 구성되는데, 이 때, 수많은 파일을 그대로 다운로드하게끔 만든다면, 그만큼 서버의 자원을 소모하고 웹사이트가 느리게 로딩된다.
=>
이를 해결하기 위해, 번들링(여러 파일을 하나로 결합하는 작업)이라는 개념이 대두되었고, 웹팩이 등장하였다.
웹팩이란 웹 애플리케이션을 구성하는 자원(HTML, CSS, JavaScript, 이미지 등)을 모두 각각의 모듈로 보고 다양한 로더와 플러그인을 사용하여 모듈을 번들링하여, 병합된 하나의 결과물을 만드는 정적 모듈 번들러이다.
2. Webpack의 특징(+ 간단 예시)
1. 성능 개선 및 자동화 기능:
웹 사이트의 로딩 시간을 단축시키기 위해 사용되지 않는 코드를 제거하는 트리 쉐이킹(Tree Shaking) 기법과 코드를 축소하는 기능을 제공하여 불필요한 HTTP 요청을 줄여, 웹 애플리케이션의 성능을 최적화
// webpack.config.js
module.exports = {
mode: 'production', // 프로덕션 모드에서 자동으로 트리 쉐이킹과 코드 축소가 적용됩니다.
optimization: {
usedExports: true, // 트리 쉐이킹을 활성화하여 사용되지 않는 코드를 제거합니다.
},
};
2. 모듈 단위의 자원 관리:
HTML, CSS, JavaScript, 이미지, 폰트 등 웹을 구성하는 다양한 자원들을 개별 모듈로 관리할 수 있게 해주며, 이를 통해 프로젝트의 구조를 명확하게 하고 유지보수를 용이하게 함
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader'], // CSS 파일을 모듈로 불러와서 사용합니다.
},
{
test: /\.(png|svg|jpg|gif)$/,
use: ['file-loader'], // 이미지 파일을 모듈로 관리합니다.
},
],
},
};
3. 풍부한 플러그인과 로더 시스템:
Sass, Stylus, TypeScript 등 다양한 전처리기(preprocessor)나 언어를 지원하기 위해 풍부한 플러그인과 로더를 제공합니다. 개발자는 이러한 도구들을 활용해 빌드 프로세스를 자동화하고, 개발 효율성을 높임
// webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
module: {
rules: [
{
test: /\.ts$/,
use: 'ts-loader', // TypeScript 파일을 처리합니다.
},
],
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html', // HTML 파일을 자동으로 생성하고, 번들을 추가합니다.
}),
],
};
4. 종속성 관리 및 해결:
프로젝트의 모든 종속성을 추적하고, 이를 하나의 번들로 합치는 과정에서 발생할 수 있는 종속성 문제를 해결해 줍니다. 이를 통해 서버와 브라우저 모두에서 원활하게 작동하는 애플리케이션을 구축할 수 있음
// entry point 예시: index.js
import _ from 'lodash'; // lodash 라이브러리를 불러옵니다.
function component() {
const element = document.createElement('div');
element.innerHTML = _.join(['Hello', 'webpack'], ' ');
return element;
}
document.body.appendChild(component());
5. 개발 및 프로덕션 모드:
개발(Development) 모드와 프로덕션(Production) 모드를 구분하여, 개발 시에는 빠른 빌드와 소스맵(sourcemap) 지원을, 프로덕션 시에는 자동 최적화 및 미니파이(minify)를 수행
// webpack.config.js
module.exports = {
mode: process.env.NODE_ENV === 'production' ? 'production' : 'development',
devtool: process.env.NODE_ENV === 'production' ? false : 'inline-source-map', // 개발 모드에서 소스맵을 사용합니다.
};
6. 코드 분할(Code Splitting):
코드 분할 기능을 통해 사용자가 실제로 필요로 하는 코드만 로드할 수 있도록 지원합니다. 이를 통해 초기 로딩 시간을 단축시키고, 애플리케이션의 성능을 향상
// webpack.config.js
module.exports = {
optimization: {
splitChunks: {
chunks: 'all', // 공통 모듈을 자동으로 분할합니다.
},
},
};
7. 핫 모듈 교체(Hot Module Replacement, HMR):
HMR 기능은 애플리케이션을 실행한 상태에서 개별 모듈을 교체할 수 있게 해주어, 개발 과정에서의 피드백 루프를 단축
// webpack.config.js
const webpack = require('webpack');
module.exports = {
entry: ['./src/index.js'],
devServer: {
hot: true, // HMR을 활성화합니다.
},
plugins: [
new webpack.HotModuleReplacementPlugin(), // HMR 플러그인을 추가합니다.
],
};
3. 구성
3_1. 패키지 설치
npm i -D webpack webpack-cli// -D는 --save-dev의 축약형으로, 패키지를 패키지를 devDependencies, 즉 개발 의존성에만 설치를 하는 것
=> 프로덕션 환경에서는 개발 단계에서 webpack을 통해 번들링된 결과물을 사용하는 것이기에, 빌드 후에 프로덕션 서버에서 실행되지 않으므로, 개발 의존성으로 분류하는 것이 일반적
3_2. webpack.config.js 설정(예시)
const path = require("path");
const webpack = require("webpack");
module.exports = {
name: "gugudan", // 프로젝트 이름 설정
mode: "development", // 모드 설정: 개발 모드에서는 소스맵을 포함하여 빌드하여 디버깅을 용이하게 함
devtool: "eval", // 개발 도구 설정: 빠른 빌드 속도를 위해 eval 사용, 개발 모드에서 추천
resolve: {
extensions: [".js", ".jsx"], // 모듈 해석 시 확장자를 생략할 수 있게 해줌
},
entry: {
app: ["./client.jsx"], // 진입점: Webpack이 빌드를 시작할 파일 지정
},
module: {
rules: [
{
test: /\.jsx?$/, // 적용 대상 파일: .js 또는 .jsx 확장자를 가진 파일
loader: "babel-loader", // 사용할 로더: Babel을 이용해 ES6 이상의 코드를 하위 호환 가능한 코드로 변환
options: {
presets: [
[
"@babel/preset-env", // Babel 프리셋 설정: 브라우저 호환성에 맞춰 자동으로 JS 변환
{
targets: { browsers: ["> 1% in KR"] }, // 대상 브라우저 설정: 한국에서 점유율이 1% 이상인 브라우저
debug: true, // 디버그 모드 활성화: 변환 시 어떤 변환이 이루어지는지 콘솔에 출력
},
],
"@babel/preset-react", // 리액트 프리셋: JSX를 일반 JS로 변환
],
plugins: ["@babel/plugin-proposal-class-properties"], // 클래스 프로퍼티 사용을 위한 플러그인
},
},
],
},
plugins: [
new webpack.LoaderOptionsPlugin({ debug: true }), // 로더에 대한 글로벌 옵션 설정: 디버그 모드 활성화
],
output: {
path: path.join(__dirname, "dist"), // 출력 파일이 저장될 경로
filename: "app.js", // 출력 파일 이름
publicPath: "/dist", // 빌드된 파일들이 위치할 서버 상의 경로
},
};
3_2. webpack.config.js 해부(module.exports 내부)
mode
모드(mode) 설정은 Webpack이 빌드할 때 사용할 모드를 지정한다.
주로 development, production, none 세 가지가 있다.
development: 개발 모드에서는 소스 맵을 포함하여 빌드하고, 빌드 속도에 최적화되어 있다.
production: 프로덕션 모드에서는 코드 압축, 최적화 등을 포함하여 빌드하고, 성능에 최적화되어 있다.
none: 아무런 기본 최적화 옵션을 적용하지 않는다.
devtool
devtool 설정은 소스 맵(source map)의 생성 방식을 지정합니다. 소스 맵은 변환된 코드를 원본 소스에 매핑하여 디버깅을 용이하게 한다.
eval: 각 모듈을 평가하기 위한 JS 코드를 생성하며, 빌드 속도가 매우 빠르다.
source-map: 별도의 소스맵 파일을 생성합니다. 정확한 소스 맵을 제공하지만 빌드 속도가 느리다.
cheap-source-map: 소스 맵을 생성할 때, 컬럼 매핑(column-mapping)을 제외하여 처리 속도를 높인다. 하지만, 이로 인해 정확한 코드 위치를 파악하기 어려울 수 있기에, 주로 개발 환경에서 빠른 빌드 속도가 필요할 때 사용된다.
cheap-module-source-map: cheap-source-map과 유사하지만, module의 소스 맵을 포함하여, loader에서 변환된 코드도 처리할 수 있다. 빌드 속도와 디버깅의 정확성 사이에 균형을 맞추고자 할 때 유용하다.
eval-source-map: 각 모듈에 대해 소스 맵을 Data URL 형태로 포함하며, 빌드 속도는 빠르지만, 결과 파일의 크기가 커질 수 있다. 따라서 개발 환경에서 자주 사용되며, 디버깅에 매우 유용하다.
eval-cheap-source-map: eval과 cheap-source-map의 특성을 결합한 옵션으로, 각 모듈에 대한 소스 맵을 Data URL로 포함하되, 컬럼 매핑을 제외하며, 빌드 속도를 개선하면서도 일부 디버깅 정보를 유지하고자 할 때 적합하다.
eval-cheap-module-source-map: eval-cheap-source-map의 확장형으로, loader로부터 변환된 코드에 대한 매핑 정보도 포함합니다. 개발 환경에서 상대적으로 빠른 빌드 속도와 함께 보다 정확한 디버깅 정보를 원할 때 사용된다.
none: 소스 맵을 생성하지 않으며,디버깅 정보가 전혀 필요하지 않을 때, 예를 들어, 최종 프로덕션 빌드 시 사용된다.
resolve
resolve 설정은 모듈 해석 방식을 지정
- extensions: 모듈을 불러올 때 확장자를 생략할 수 있게 해준다. 예를 들어, .js, .jsx를 지정하면, 이 확장자를 가진 파일을 불러올 때 확장자를 명시하지 않아도 된다.
entry
entry 설정은 Webpack이 빌드를 시작할 진입점을 지정한다. 객체 형태로 여러 진입점을 지정할 수 있으며, 각각의 진입점에 대해 하나의 번들 파일이 생성한다.
module
module 설정은 모듈 처리 방법을 지정한다.
- rules: 모듈(파일)에 적용할 규칙(로더)을 배열로 설정한다. 각 규칙은 테스트(적용할 파일 패턴), 사용할 로더, 로더에 전달할 옵션 등을 지정할 수 있다.
- test: 규칙이 적용될 파일을 결정하는 정규 표현식이다. 예를 들어, /\.jsx?$/는 .js 또는 .jsx 확장자를 가진 파일에 이 규칙을 적용한다.
- loader: 파일 변환, 소스 코드 변환, 이미지 최적화, 코드 품질 및 스타일 가이드 적용 등의 역할을 한다.
- babel-loader: JavaScript 코드를 변환하기 위해 사용된다. ES6 이상의 코드를 하위 호환 가능한 코드로 변환해주며, JSX 같은 확장 문법도 처리할 수 있다.
- options: presets와 plugins를 사용하여 변환 방식을 세밀하게 조정할 수 있다.
- css-loader / style-loader: CSS 파일을 처리하기 위해 사용된다. css-loader는 CSS 파일을 JavaScript 모듈로 변환하고, style-loader는 변환된 스타일을 동적으로 DOM에 삽입한다.
- options: CSS 모듈 활성화, 소스 맵 생성 등을 설정할 수 있다.
- file-loader / url-loader: 이미지, 폰트 등의 파일을 처리하기 위해 사용된다. file-loader는 파일을 출력 디렉토리로 복사하고 URL을 반환한다. url-loader는 파일 크기가 제한보다 작으면 데이터 URL로 변환하고, 그렇지 않으면 file-loader처럼 동작한다.
- options: 파일 이름 패턴, 데이터 URL 변환 기준 크기 등을 설정할 수 있다.
- babel-loader: JavaScript 코드를 변환하기 위해 사용된다. ES6 이상의 코드를 하위 호환 가능한 코드로 변환해주며, JSX 같은 확장 문법도 처리할 수 있다.
plugins
plugins 설정은 빌드 과정에 적용할 플러그인을 배열로 지정한다. 플러그인을 통해 번들 최적화, 환경 변수 주입 등 다양한 작업을 수행할 수 있다.
범용적인 플러그인을 몇 개 소개하고 넘어가겠다.
- HtmlWebpackPlugin
자동으로 HTML 파일을 생성하고, 생성된 번들을 HTML 파일에 자동으로 주입하여, 개발 과정에서 HTML 파일을 수동으로 관리하는 번거로움을 줄여준다. - CleanWebpackPlugin
빌드 전에 이전에 생성된 파일들을 제거하여, 빌드 디렉토리를 깨끗하게 유지하는 데 도움을 준다. - MiniCssExtractPlugin
CSS 파일을 별도의 파일로 추출하여, CSS와 JavaScript가 분리되어 로딩될 수 있도록 하여, 브라우저 캐싱의 이점을 누리고, 비동기적으로 CSS를 로딩할 수 있게 한다. - DefinePlugin
빌드 타임에 환경 변수를 정의할 수 있다. 예를 들어, 개발 환경과 프로덕션 환경에서 다른 API 엔드포인트를 사용해야 할 때 유용하다. - TerserWebpackPlugin
JavaScript 코드를 압축하고 난독화하여 번들의 크기를 줄여, 프로덕션 환경에서 웹 사이트의 로딩 시간을 개선하는 데 도움이 된다.
output
output 설정은 빌드된 파일의 출력 방식을 지정한다.
- path: 출력 파일이 저장될 경로를 지정한다.
- filename: 출력 파일의 이름을 지정한다. 진입점별로 다른 파일 이름을 생성하려면 [name].js와 같은 템플릿을 사용할 수 있다.
- publicPath: 빌드된 파일들이 위치할 서버 상의 경로를 지정한다. 예를 들어, CDN을 사용하는 경우 여기에 CDN의 주소를 지정할 수 있다.