Перейти к содержимому

17. Реальная конфигурация

Финальный урок курса — полный production-ready webpack.config.js для React + TypeScript приложения со всеми необходимыми оптимизациями. Это конфиг, который используется в реальных enterprise-проектах.

const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const CopyPlugin = require('copy-webpack-plugin');
const ForkTsCheckerPlugin = require('fork-ts-checker-webpack-plugin');
const ReactRefreshPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
const isDev = process.env.NODE_ENV === 'development';
const isProd = !isDev;
const isAnalyze = process.env.ANALYZE === 'true';
module.exports = {
// ─────────────────────────── Core ───────────────────────────
mode: isDev ? 'development' : 'production',
target: 'browserslist',
bail: isProd, // Остановить сборку при первой ошибке в production
// ─────────────────────────── Entry ──────────────────────────
entry: {
main: './src/index.tsx',
},
// ─────────────────────────── Output ─────────────────────────
output: {
path: path.resolve(__dirname, 'dist'),
filename: isProd ? '[name].[contenthash:8].js' : '[name].js',
chunkFilename: isProd ? '[id].[contenthash:8].js' : '[id].js',
assetModuleFilename: 'assets/[hash:8][ext]',
publicPath: '/',
clean: true,
},
// ─────────────────────────── Resolve ────────────────────────
resolve: {
extensions: ['.tsx', '.ts', '.js', '.jsx'],
alias: {
'@': path.resolve(__dirname, 'src'),
'@api': path.resolve(__dirname, 'src/api'),
'@components': path.resolve(__dirname, 'src/components'),
'@hooks': path.resolve(__dirname, 'src/hooks'),
'@utils': path.resolve(__dirname, 'src/utils'),
},
modules: ['node_modules'],
symlinks: false,
},
// ─────────────────────────── Loaders ────────────────────────
module: {
rules: [
// JS/TS + JSX/TSX
{
test: /\\.([jt]sx?)$/,
use: {
loader: 'babel-loader',
options: {
presets: [
['@babel/preset-env', { targets: 'defaults', useBuiltIns: 'usage', corejs: 3 }],
['@babel/preset-react', { runtime: 'automatic' }],
'@babel/preset-typescript',
],
plugins: [isDev && 'react-refresh/babel'].filter(Boolean),
cacheDirectory: true, // Кеш Babel
},
},
exclude: /node_modules/,
},
// Global CSS
{
test: /\\.css$/,
exclude: /\\.module\\.css$/,
use: [
isDev ? 'style-loader' : MiniCssExtractPlugin.loader,
{ loader: 'css-loader', options: { importLoaders: 1, sourceMap: isDev } },
{ loader: 'postcss-loader', options: { postcssOptions: { plugins: ['autoprefixer'] } } },
],
},
// CSS Modules
{
test: /\\.module\\.css$/,
use: [
isDev ? 'style-loader' : MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
importLoaders: 1,
modules: { localIdentName: isDev ? '[name]__[local]' : '[hash:base64:8]' },
},
},
],
},
// SCSS
{
test: /\\.s[ac]ss$/,
use: [
isDev ? 'style-loader' : MiniCssExtractPlugin.loader,
{ loader: 'css-loader', options: { importLoaders: 3 } },
'postcss-loader',
'sass-loader',
],
},
// Images
{
test: /\\.(png|jpg|jpeg|gif|webp|avif)$/i,
type: 'asset',
parser: { dataUrlCondition: { maxSize: 8192 } },
generator: { filename: 'images/[hash:8][ext]' },
},
// SVG
{ test: /\\.svg$/, type: 'asset/resource', generator: { filename: 'icons/[hash:8][ext]' } },
// Fonts
{
test: /\\.(woff|woff2|eot|ttf|otf)$/i,
type: 'asset/resource',
generator: { filename: 'fonts/[name][ext]' },
},
],
},
// ─────────────────────────── Plugins ────────────────────────
plugins: [
new HtmlWebpackPlugin({ template: './public/index.html', minify: isProd }),
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
'process.env.APP_VERSION': JSON.stringify(process.env.npm_package_version),
}),
isProd && new MiniCssExtractPlugin({ filename: '[name].[contenthash:8].css' }),
isDev && new ReactRefreshPlugin(),
new ForkTsCheckerPlugin({ async: isDev }),
isProd && new CopyPlugin({ patterns: [{ from: 'public', filter: (f) => !f.endsWith('.html') }] }),
isAnalyze && new BundleAnalyzerPlugin({ analyzerMode: 'static', openAnalyzer: false }),
].filter(Boolean),
// ─────────────────────────── Optimization ───────────────────
optimization: {
minimize: isProd,
minimizer: [
new TerserPlugin({
parallel: true,
terserOptions: { compress: { drop_console: isProd }, format: { comments: false } },
extractComments: false,
}),
new CssMinimizerPlugin(),
],
splitChunks: {
chunks: 'all',
cacheGroups: {
react: { test: /[\\/]node_modules[\\/](react|react-dom|react-router)[\\/]/, name: 'react-vendor', priority: 30, chunks: 'all' },
vendors: { test: /[\\/]node_modules[\\/]/, name: 'vendors', priority: 20, chunks: 'all' },
common: { name: 'common', minChunks: 2, priority: 10, reuseExistingChunk: true },
},
},
runtimeChunk: 'single',
moduleIds: 'deterministic',
},
// ─────────────────────────── Dev Server ─────────────────────
devServer: isDev ? {
port: 3000,
hot: true,
historyApiFallback: true,
compress: true,
proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } },
} : undefined,
// ─────────────────────────── Cache ──────────────────────────
cache: {
type: 'filesystem',
buildDependencies: { config: [__filename] },
},
// ─────────────────────────── Performance ────────────────────
performance: isProd ? {
hints: 'warning',
maxEntrypointSize: 300000, // 300 KB
maxAssetSize: 300000,
} : false,
// ─────────────────────────── DevTool ────────────────────────
devtool: isDev ? 'eval-cheap-module-source-map' : 'source-map',
// ─────────────────────────── Stats ──────────────────────────
stats: isDev ? 'errors-warnings' : 'normal',
};
dist/
├── index.html
├── runtime.abc12345.js (2 KB) ← Webpack runtime
├── main.def67890.js (85 KB) ← Ваш код
├── react-vendor.ghi23456.js (95 KB) ← React + React-DOM
├── vendors.jkl78901.js (68 KB) ← node_modules
├── common.mno23456.js (24 KB) ← Общий код
├── main.pqr78901.css (18 KB) ← CSS
├── assets/
│ ├── logo.abc12345.png
│ └── hero.def67890.webp
├── fonts/
│ └── Inter-Regular.woff2
└── icons/
└── icon-check.ghi23456.svg

Ниже — интерактивный Webpack Stats Dashboard — финальный playground курса. Полная аналитика сборки: размеры чанков, граф зависимостей, метрики производительности.