"Webapck이란 Javascript 기반의 모듈 번들러다."
Webpack에 대한 정의를 검색할 경우 많이 볼 수 있는 문장이다. 여기서 말하는 모듈과 번들러란 뭘까?
Javascript의 변수는 기본적으로 전역 범위를 가진다. 이 경우 아래와 같은 문제가 생길 수 있다.
// testA.js
var num = 10;
// testB.js
var num = 20;
// index.html
<body>
<script src="./testA.js"></script>
<script src="./testB.js"></script>
<script>
console.log(num); // 20
</script>
</body>
testA의 변수 num과 testB의 변수 num이 전역변수기 때문에 main.js 에서 어디서든 접근할 수 있게된다. 이런 경우 변수가 중복되어 다른 사람이 작성한 변수를 내가 모르고 변경해버리는 일이 생길 수 있다.
이러한 문제를 해결하기 위해서 CommonJS와 AMD(Asynchronous Module Definition)이 등장했다. 모듈을 export, import/require 하는 과정을 통해 모듈의 스코프를 지정하고 독립적으로 사용가능하다.
'번들링한다'의 의미는 우리가 만든 자원(HTML, CSS, Javascript, Image 등)을 하나의 파일 또는 여러개의 파일로 묶어주는 과정을 의미한다.
여러개의 파일로 나누어져있는 모듈을 병합하고 압축하여 크기를 줄일 수 있다. 또한 서버와 HTTP 통신 시 모듈이 각각 나눠져있다면 요청을 여러번 보내야 하지만 번들링을 통해 압축된 번들에 대한 요청 하나로 처리할 수 있다.
번들링의 장점
- 원본 파일에 비해 실행 시 속도를 높일 수 있다.
- 거대해진 코드를 번들을 통해 나누어 관리할 수 있다.
- 여러 모듈이 압축된 파일을 요청하므로 네트워크 비용을 줄일 수 있다.
따라서 Webapck이 등장하게 된 배경을 요약하자면 다음과 같다.
React환경에서는 하나의 HTML파일에 여러 Javascript 파일이 포함되므로 Webpack과 같은 번들러를 통해 묶어줄 필요가 있다.
Webpack의 주요 구성요소는 크게 Entry, Output, Loaders, Plugins로 되어있다.
웹팩의 빌드가 시작되는 지점. Webpack이 해석할 JS 파일의 위치를 명시한다.
module.exports = {
entry: './src/index.tsx', // src폴더 안에 있는 index.tsx파일부터 시작
};
빌드를 통해 생성된 번들을 내보낼 경로, 이름을 지정하는 역할 수행
module.exports = {
output: {
path: path.resolve(__dirname, 'dist'), // 출력 파일 디렉토리
filename: '[name].[chunkhash].bundle.js', // 매 빌드 시, 번들 구분을 위해 이름을 chunkhash로 생성
},
};
여기서 path.resolve(__dirname, 'dist')
를 사용한 이유는 Window와 Linux의 경로 구분자가 다르므로 path 모듈을 통해 절대 경로를 지정하기 위해 사용한 것.
(현재 파일(webpack.config.js)의 위치/dist)
Webpack을 기본적으로 Jacascript와 JSON 파일만 이해할 수 있다. 하지만 CSS, 이미지 등 다른 종류의 파일을 처리하기 위해 Loader를 사용해 번들링할 수 있다. Loader가 여러개인 경우 오른쪽에서 왼쪽 순으로 적용됩니다.
module.exports = {
module: {
rules: [
{
test: /\.(js|jsx|ts|tsx)$/,
// 확장자가 js,jsx,ts,tsx 인 파일을 보면 esbuild-loader를 통해 변환해줘
exclude: /node_modules/,
// node_modules에 있는 파일은 제외
loader: 'esbuild-loader',
options: {
target: 'es2021',
loader: 'tsx',
minify: true, // 트랜스파일링하며 압축 진행
},
},
{
test: /\.css$/,
exclude: /node_modules/,
use: ['style-loader', 'css-loader'], // css-loader먼저 적용 후 style-loader 적용
},
],
},
};
test 부분 정규식에는 따옴표를 붙이지 않도록 주의!
Webpack의 부가적인 플러그인을 추가해 기능을 확장한다. Loader는 특정 유형의 모듈(CSS, 이미지 등)을 변환하는데 사용하지만 Plugin은 좀 더 넓은 범위의 작업을 수행한다.
아래의 예시처럼 환경변수 파일인 .env
관리, HTML 파일에 script태그 자동생성 등의 역할을 수행할 수 있다.
module.exports = {
plugins: [
new webpack.ProvidePlugin({
React: 'react',
}), // 자주 사용하는 모듈 requier, import 없이 사용
new HtmlWebpackPlugin({
template: path.resolve(__dirname, './public/index.html'),
favicon: path.resolve(__dirname, './public/icons/favicon.ico'),
manifest: path.resolve(__dirname, './public/manifest.json'),
}), // 번들링 결과를 html파일에 삽입할 수 있게 도와주는 Plugin
new CopyPlugin({
patterns: [{ from: 'public/icons', to: '' }],
}), // public 폴더의 아이콘과 같은 정적 파일을 dist에 넣어주는 Plugin
new Dotenv(), // 환경변수 관련 Plugin
],
};
이외에도 공식문서의 다양한 설정 방법을 통해 Webpack을 세팅할 수 있다.
프로젝트를 하다보면 개발환경과 실제 배포환경에서의 설정을 다르게 해야하는 경우가 생긴다. Webpack 설정도 개발환경과 배포환경을 나누어 입맛에 맞게 커스텀할 수 있다.
...
├─webpack.common.js
├─webpack.dev.js
└─webpack.prod.js
위와 같은 구조를 통해 webpack.common.js
에서는 Entry, Ouput과 같은 공통 설정을 한다.
이후 webpack.dev.js
에는 dev server, source map 등 개발환경에서 필요한 설정을 하고 webpack.prod.js
에는 Analyzer, chunk최적화 등의 작업을 설정할 수 있다.
// webpack.dev.js
const common = require('./webpack.common');
module.exports = merge(common, {
mode: 'development',
devtool: 'eval-cheap-module-source-map',
devServer: {
hot: true,
open: true,
historyApiFallback: true,
port: 3000,
static: path.resolve(__dirname, 'dist'),
headers: {
'Cross-Origin-Opener-Policy': 'same-origin',
'Cross-Origin-Embedder-Policy': 'require-corp',
},
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
new CopyPlugin({
patterns: [{ from: 'public/mockServiceWorker.js', to: '' }],
}),
],
});
// webpack.prod.js
const common = require('./webpack.common');
module.exports = merge(common, {
mode: 'production',
devtool: 'nosources-source-map',
optimization: {
splitChunks: {
chunks: 'all',
},
},
plugins: [
new BundleAnalyzerPlugin({
analyzerMode: 'static',
openAnalyzer: false,
generateStatsFile: true,
statsFilename: 'bundle-report.json',
}),
],
});