Налаштування бандлера Webpack для веб-проекту
Webpack — найбільш гнучкий і широко підтримуваний бандлер для фронтенду. Підтримує будь-який стек, будь-які трансформації, Module Federation, складні стратегії split-chunks. Вимагає конфігурації, але поведінка передбачувана і добре документована.
Використовується там, де потрібні можливості, недоступні у Vite: Module Federation, складний code splitting, нестандартні лоадери, підтримка IE11 (якщо все ще актуально), інтеграція з існуючими webpack-плагінами.
Що входить до роботи
Налаштування webpack.config.ts для dev та prod, Babel/SWC, TypeScript, CSS/PostCSS, активи, code splitting, tree shaking, аналіз bundle, dev server з HMR.
Встановлення
npm install -D webpack webpack-cli webpack-dev-server
npm install -D html-webpack-plugin mini-css-extract-plugin css-minimizer-webpack-plugin
npm install -D terser-webpack-plugin compression-webpack-plugin
npm install -D babel-loader @babel/core @babel/preset-env @babel/preset-react @babel/preset-typescript
# або SWC замість Babel (швидше)
npm install -D swc-loader @swc/core @swc/helpers
webpack.config.ts — базова структура
import path from 'path'
import webpack from 'webpack'
import HtmlWebpackPlugin from 'html-webpack-plugin'
import MiniCssExtractPlugin from 'mini-css-extract-plugin'
import CssMinimizerPlugin from 'css-minimizer-webpack-plugin'
import TerserPlugin from 'terser-webpack-plugin'
import CompressionPlugin from 'compression-webpack-plugin'
const isDev = process.env.NODE_ENV !== 'production'
const root = path.resolve(__dirname)
const config: webpack.Configuration = {
mode: isDev ? 'development' : 'production',
entry: {
main: './src/index.tsx',
},
output: {
path: path.resolve(root, 'dist'),
filename: isDev ? '[name].js' : '[name].[contenthash:8].js',
chunkFilename: isDev ? '[name].chunk.js' : '[name].[contenthash:8].chunk.js',
assetModuleFilename: 'assets/[hash][ext][query]',
publicPath: '/',
clean: true,
},
resolve: {
extensions: ['.ts', '.tsx', '.js', '.jsx'],
alias: {
'@': path.resolve(root, 'src'),
'@components': path.resolve(root, 'src/components'),
'@hooks': path.resolve(root, 'src/hooks'),
'@utils': path.resolve(root, 'src/utils'),
},
},
module: {
rules: [
// TypeScript + React через SWC
{
test: /\.(ts|tsx|js|jsx)$/,
exclude: /node_modules/,
use: {
loader: 'swc-loader',
options: {
jsc: {
parser: { syntax: 'typescript', tsx: true },
transform: {
react: {
runtime: 'automatic',
development: isDev,
refresh: isDev,
},
},
target: 'es2020',
},
},
},
},
// CSS + Modules + PostCSS
{
test: /\.css$/,
use: [
isDev ? 'style-loader' : MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
modules: {
auto: /\.module\.css$/,
localIdentName: isDev ? '[local]--[hash:base64:5]' : '[hash:base64:8]',
},
importLoaders: 1,
},
},
'postcss-loader',
],
},
// Статичні активи
{
test: /\.(png|jpg|webp|gif|svg)$/,
type: 'asset',
parser: {
dataUrlCondition: { maxSize: 4 * 1024 }, // < 4kb → inline
},
},
{
test: /\.(woff2?|ttf|eot)$/,
type: 'asset/resource',
},
],
},
plugins: [
new HtmlWebpackPlugin({
template: './public/index.html',
favicon: './public/favicon.ico',
minify: !isDev,
}),
!isDev && new MiniCssExtractPlugin({
filename: 'css/[name].[contenthash:8].css',
chunkFilename: 'css/[name].[contenthash:8].chunk.css',
}),
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
'process.env.API_URL': JSON.stringify(process.env.API_URL),
}),
!isDev && new CompressionPlugin({
algorithm: 'brotliCompress',
test: /\.(js|css|html|svg)$/,
threshold: 10240,
}),
].filter(Boolean),
optimization: {
minimize: !isDev,
minimizer: [
new TerserPlugin({
terserOptions: {
compress: { drop_console: true },
format: { comments: false },
},
extractComments: false,
}),
new CssMinimizerPlugin(),
],
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/](react|react-dom|react-router-dom)[\\/]/,
name: 'vendor-react',
chunks: 'all',
priority: 20,
},
commons: {
test: /[\\/]node_modules[\\/]/,
name: 'vendor-commons',
chunks: 'all',
priority: 10,
minChunks: 2,
},
},
},
runtimeChunk: 'single',
moduleIds: isDev ? 'named' : 'deterministic',
chunkIds: isDev ? 'named' : 'deterministic',
},
devServer: {
port: 3000,
hot: true,
historyApiFallback: true,
compress: true,
proxy: [
{
context: ['/api'],
target: 'http://localhost:8000',
changeOrigin: true,
},
],
client: {
overlay: { errors: true, warnings: false },
},
},
devtool: isDev ? 'eval-cheap-module-source-map' : 'source-map',
performance: {
hints: isDev ? false : 'warning',
maxAssetSize: 250_000,
maxEntrypointSize: 500_000,
},
}
export default config
postcss.config.js
module.exports = {
plugins: [
require('postcss-import'),
require('tailwindcss'),
require('autoprefixer'),
!process.env.DEV && require('cssnano')({ preset: 'default' }),
].filter(Boolean),
}
.swcrc (альтернатива — окремий файл конфігу SWC)
{
"$schema": "https://json.schemastore.org/swcrc",
"jsc": {
"parser": {
"syntax": "typescript",
"tsx": true,
"decorators": true
},
"transform": {
"react": {
"runtime": "automatic"
},
"legacyDecorator": true
},
"target": "es2020",
"loose": false,
"externalHelpers": true
},
"module": {
"type": "es6"
}
}
Аналіз bundle
npm install -D webpack-bundle-analyzer
// додати до plugins при ANALYZE=true
process.env.ANALYZE && new BundleAnalyzerPlugin({
analyzerMode: 'static',
reportFilename: 'bundle-report.html',
openAnalyzer: false,
generateStatsFile: true,
})
ANALYZE=true npm run build
# відкриваємо dist/bundle-report.html
Lazy loading маршрутів
// src/App.tsx
const ProductsPage = lazy(() =>
import(/* webpackChunkName: "products" */ './pages/ProductsPage')
)
const CheckoutPage = lazy(() =>
import(/* webpackChunkName: "checkout" */ './pages/CheckoutPage')
)
// prefetch при idle
const AdminPage = lazy(() =>
import(/* webpackChunkName: "admin", webpackPrefetch: true */ './pages/AdminPage')
)
TypeScript-шляхи в tsconfig
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@components/*": ["src/components/*"],
"@hooks/*": ["src/hooks/*"]
}
}
}
Шляхи в tsconfig потрібно дублювати в resolve.alias — webpack не читає tsconfig автоматично.
Змінні окружння через .env
npm install -D dotenv-webpack
import Dotenv from 'dotenv-webpack'
plugins: [
new Dotenv({
path: `.env.${process.env.NODE_ENV}`,
safe: true, // перевіряє .env.example
systemvars: true, // env vars з окружння мають пріоритет
}),
]
Скрипти в package.json
{
"scripts": {
"dev": "webpack serve --mode development",
"build": "webpack --mode production",
"build:analyze": "ANALYZE=true webpack --mode production",
"typecheck": "tsc --noEmit"
}
}
Що ми робимо
Налаштовуємо webpack із SWC (або Babel) для TypeScript + React, CSS Modules, PostCSS, оптимізуємо code splitting під конкретний проект, налаштовуємо dev server з proxy на backend, додаємо bundle analyzer, налаштовуємо стиснення brotli для production.
Терміни: 1–3 дні залежно від складності стека та кількості нестандартних вимог.







