Webpack Bundling: Tối Ưu Performance Cho Frontend
Hướng dẫn cấu hình Webpack cho production-ready applications
Webpack Bundling: Tối Ưu Performance Cho Frontend
Webpack là module bundler mạnh mẽ cho phép bạn optimize và manage dependencies hiệu quả. Hiểu rõ cách cấu hình Webpack sẽ giúp improve performance đáng kể cho ứng dụng của bạn.
Webpack Basics
Core Concepts
// webpack.config.js
const path = require('path');
module.exports = {
// Entry point
entry: './src/index.js',
// Output configuration
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[contenthash].js',
clean: true
},
// Loaders
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env', '@babel/preset-react']
}
}
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
},
{
test: /\.(png|svg|jpg|jpeg|gif)$/i,
type: 'asset/resource'
}
]
},
// Plugins
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html'
})
]
};
Entry và Output
// Multiple entry points
module.exports = {
entry: {
main: './src/index.js',
vendor: './src/vendor.js',
admin: './src/admin.js'
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[contenthash].bundle.js',
chunkFilename: '[name].[contenthash].chunk.js'
}
};
Code Splitting Strategies
1. Entry Points Splitting
module.exports = {
entry: {
app: './src/app.js',
admin: './src/admin.js'
},
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all'
}
}
}
}
};
2. Dynamic Import
// Before: Single large bundle
import { heavyCalculation } from './utils/heavy';
// After: Lazy load when needed
const loadHeavyCalculation = async () => {
const { heavyCalculation } = await import('./utils/heavy');
return heavyCalculation;
};
// React lazy loading
import { lazy, Suspense } from 'react';
const HeavyComponent = lazy(() => import('./components/HeavyComponent'));
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<HeavyComponent />
</Suspense>
);
}
3. Route-based Splitting
// React Router v6
import { lazy } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));
const Dashboard = lazy(() => import('./pages/Dashboard'));
function App() {
return (
<BrowserRouter>
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/dashboard" element={<Dashboard />} />
</Routes>
</Suspense>
</BrowserRouter>
);
}
Optimization Techniques
1. Tree Shaking
// webpack.config.js
module.exports = {
mode: 'production',
optimization: {
usedExports: true,
sideEffects: false
}
};
// package.json
{
"sideEffects": [
"*.css",
"*.scss"
]
};
2. Minification và Compression
const TerserPlugin = require('terser-webpack-plugin');
const CompressionPlugin = require('compression-webpack-plugin');
module.exports = {
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
terserOptions: {
compress: {
drop_console: true,
drop_debugger: true
}
}
})
]
},
plugins: [
new CompressionPlugin({
algorithm: 'gzip',
test: /\.(js|css|html|svg)$/,
threshold: 8192,
minRatio: 0.8
})
]
};
3. Image Optimization
const ImageMinimizerPlugin = require('image-minimizer-webpack-plugin');
module.exports = {
module: {
rules: [
{
test: /\.(jpe?g|png|gif|svg)$/i,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 8192 // 8kb
}
}
}
]
},
plugins: [
new ImageMinimizerPlugin({
minimizer: {
implementation: ImageMinimizerPlugin.imageminMinify,
options: {
plugins: [
['gifsicle', { interlaced: true }],
['jpegtran', { progressive: true }],
['optipng', { optimizationLevel: 5 }]
]
}
}
})
]
};
Development vs Production
Development Configuration
const { merge } = require('webpack-merge');
const common = require('./webpack.common.js');
module.exports = merge(common, {
mode: 'development',
devtool: 'eval-cheap-module-source-map',
devServer: {
static: './dist',
hot: true,
open: true,
historyApiFallback: true,
compress: true,
port: 3000
},
optimization: {
splitChunks: {
chunks: 'all'
}
}
});
Production Configuration
const { merge } = require('webpack-merge');
const common = require('./webpack.common.js');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = merge(common, {
mode: 'production',
devtool: 'source-map',
output: {
filename: '[name].[contenthash].js',
chunkFilename: '[name].[contenthash].chunk.js',
clean: true
},
module: {
rules: [
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader'
]
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: '[name].[contenthash].css',
chunkFilename: '[name].[contenthash].chunk.css'
})
],
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all'
},
common: {
name: 'common',
minChunks: 2,
chunks: 'all',
enforce: true
}
}
}
}
});
Performance Monitoring
Bundle Analysis
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
plugins: [
new BundleAnalyzerPlugin({
analyzerMode: 'static',
openAnalyzer: false,
reportFilename: 'bundle-report.html'
})
]
};
Performance Budget
module.exports = {
performance: {
maxAssetSize: 250000, // 250kb
maxEntrypointSize: 250000,
hints: 'warning',
assetFilter: function(assetFilename) {
return assetFilename.endsWith('.js');
}
}
};
Advanced Techniques
Module Federation
// Host application
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'host',
remotes: {
app1: 'app1@http://localhost:3001/remoteEntry.js',
app2: 'app2@http://localhost:3002/remoteEntry.js'
}
})
]
};
// Remote application
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'app1',
filename: 'remoteEntry.js',
exposes: {
'./Button': './src/components/Button',
'./Header': './src/components/Header'
}
})
]
};
Web Workers
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.worker\.js$/,
use: { loader: 'worker-loader' }
}
]
}
};
// heavy-calculation.worker.js
self.onmessage = function(e) {
const result = performHeavyCalculation(e.data);
self.postMessage(result);
};
// main.js
import HeavyWorker from './heavy-calculation.worker.js';
const worker = new HeavyWorker();
worker.postMessage(data);
worker.onmessage = function(e) {
console.log('Calculation result:', e.data);
};
Build Optimization Checklist
✅ Code splitting implemented
✅ Tree shaking enabled
✅ Minification configured
✅ Compression enabled (gzip/brotli)
✅ Images optimized
✅ Fonts subsetted
✅ Unused CSS removed
✅ Source maps for production
✅ Performance budget set
✅ Bundle analysis performed
Kết luận
Webpack configuration tốt sẽ giúp:
- Reduce bundle size đáng kể
- Improve loading performance
- Better caching strategy
- Enhanced development experience
Bài viết này là phần đầu tiên trong series về build tools. Trong các bài tiếp theo, chúng ta sẽ explore Vite, Rollup và esbuild.