Webpack 配置示例
Webpack配置示例,包括基础设置、优化、加载器、插件和现代webpack模式
💻 Webpack 基础设置 javascript
🟢 simple
⭐⭐
JavaScript应用程序的基本webpack配置,包含开发和生产环境
⏱️ 20 min
🏷️ webpack, configuration, build
Prerequisites:
Node.js knowledge, JavaScript ES6+, Build tools concepts
// Webpack 5 Basic Configuration
// File: webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = (env, argv) => {
const isProduction = argv.mode === 'production';
return {
// Entry point
entry: './src/index.js',
// Output configuration
output: {
path: path.resolve(__dirname, 'dist'),
filename: isProduction ? '[name].[contenthash].js' : '[name].js',
chunkFilename: isProduction ? '[name].[contenthash].chunk.js' : '[name].chunk.js',
clean: true, // Clean output directory before emit
publicPath: '/'
},
// Module resolution
resolve: {
extensions: ['.js', '.jsx', '.ts', '.tsx'],
alias: {
'@': path.resolve(__dirname, 'src'),
'@components': path.resolve(__dirname, 'src/components'),
'@utils': path.resolve(__dirname, 'src/utils')
}
},
// Loaders configuration
module: {
rules: [
// JavaScript/TypeScript files
{
test: /\.(js|jsx|ts|tsx)$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: [
'@babel/preset-env',
'@babel/preset-react',
'@babel/preset-typescript'
],
plugins: [
'@babel/plugin-transform-runtime',
'@babel/plugin-proposal-class-properties'
]
}
}
},
// CSS files
{
test: /\.css$/,
use: [
isProduction ? MiniCssExtractPlugin.loader : 'style-loader',
'css-loader',
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
['autoprefixer']
]
}
}
}
]
},
// SASS/SCSS files
{
test: /\.(scss|sass)$/,
use: [
isProduction ? MiniCssExtractPlugin.loader : 'style-loader',
'css-loader',
'sass-loader'
]
},
// Images
{
test: /\.(png|jpe?g|gif|svg|webp)$/,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 8 * 1024 // 8kb
}
},
generator: {
filename: 'images/[name].[hash][ext]'
}
},
// Fonts
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
type: 'asset/resource',
generator: {
filename: 'fonts/[name].[hash][ext]'
}
},
// JSON files
{
test: /\.json$/,
type: 'asset/resource'
}
]
},
// Plugins
plugins: [
// HTML template
new HtmlWebpackPlugin({
template: './public/index.html',
filename: 'index.html',
inject: 'body',
minify: isProduction ? {
removeComments: true,
collapseWhitespace: true,
removeRedundantAttributes: true,
useShortDoctype: true,
removeEmptyAttributes: true,
removeStyleLinkTypeAttributes: true,
keepClosingSlash: true,
minifyJS: true,
minifyCSS: true,
minifyURLs: true
} : false
}),
// Environment variables
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(argv.mode),
'process.env.PUBLIC_URL': JSON.stringify(''),
__DEV__: !isProduction,
__PROD__: isProduction
})
].concat(isProduction ? [
// Production-only plugins
new MiniCssExtractPlugin({
filename: 'styles/[name].[contenthash].css',
chunkFilename: 'styles/[name].[contenthash].chunk.css'
}),
// Bundle analyzer
new BundleAnalyzerPlugin.BundleAnalyzerPlugin({
analyzerMode: 'static',
openAnalyzer: false
})
] : []),
// Development server configuration
devServer: {
contentBase: path.join(__dirname, 'dist'),
compress: true,
port: 3000,
hot: true,
open: true,
historyApiFallback: true,
overlay: {
errors: true,
warnings: false
},
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
secure: false
}
}
},
// Optimization
optimization: {
minimize: isProduction,
minimizer: [
'...', // Use default minimizer
new TerserPlugin({
parallel: true,
terserOptions: {
compress: {
drop_console: isProduction
}
}
}),
new CssMinimizerPlugin()
],
// Code splitting
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all',
priority: 10
},
common: {
name: 'common',
minChunks: 2,
chunks: 'all',
priority: 5,
reuseExistingChunk: true
}
}
},
// Runtime chunk
runtimeChunk: {
name: 'runtime'
},
// Module IDs
moduleIds: isProduction ? 'deterministic' : 'named',
chunkIds: isProduction ? 'deterministic' : 'named'
},
// Source maps
devtool: isProduction ? 'source-map' : 'eval-cheap-module-source-map',
// Performance hints
performance: {
hints: isProduction ? 'warning' : false,
maxEntrypointSize: 512000,
maxAssetSize: 512000
},
// Stats configuration
stats: {
colors: true,
modules: false,
children: false,
chunks: false,
chunkModules: false
},
// Externals (optional)
externals: {
// jquery: 'jQuery',
// lodash: '_'
}
};
};
// Required plugins for production
const webpack = require('webpack');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer');
// Package.json scripts
{
"scripts": {
"dev": "webpack serve --mode development",
"build": "webpack --mode production",
"build:analyze": "webpack --mode production --env analyze",
"clean": "rimraf dist"
}
}
// .babelrc configuration
{
"presets": [
["@babel/preset-env", {
"targets": {
"browsers": ["last 2 versions", "not dead"]
},
"modules": false,
"useBuiltIns": "usage",
"corejs": 3
}],
"@babel/preset-react",
"@babel/preset-typescript"
],
"plugins": [
["@babel/plugin-transform-runtime", {
"corejs": 3
}],
"@babel/plugin-proposal-class-properties",
"@babel/plugin-proposal-object-rest-spread"
]
}
// tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "node",
"lib": ["DOM", "DOM.Iterable", "ES6"],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "ESNext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@components/*": ["src/components/*"],
"@utils/*": ["src/utils/*"]
}
},
"include": [
"src"
]
}
// postcss.config.js
module.exports = {
plugins: [
require('autoprefixer')
]
}
💻 Webpack 高级优化 javascript
🟡 intermediate
⭐⭐⭐⭐
性能优化、代码分割、tree shaking和webpack高级功能
⏱️ 40 min
🏷️ webpack, optimization, performance
Prerequisites:
Webpack basics, JavaScript performance, Build optimization
// Webpack Advanced Optimization Configuration
// webpack.config.prod.js
const path = require('path');
const webpack = require('webpack');
const TerserPlugin = require('terser-webpack-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const { PurgeCSSPlugin } = require('purgecss-webpack-plugin');
const CompressionPlugin = require('compression-webpack-plugin');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer');
const HardSourceWebpackPlugin = require('hard-source-webpack-plugin');
const SpeedMeasurePlugin = require('speed-measure-webpack-plugin');
const { NormalModuleReplacementPlugin } = require('webpack');
// Speed measurement wrapper
const smp = new SpeedMeasurePlugin();
module.exports = smp.wrap({
mode: 'production',
// Multiple entry points for better caching
entry: {
main: './src/index.js',
vendor: ['react', 'react-dom'],
polyfills: './src/polyfills.js'
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'js/[name].[contenthash:8].js',
chunkFilename: 'js/[name].[contenthash:8].chunk.js',
publicPath: '/',
pathinfo: false,
crossOriginLoading: 'anonymous' // For better caching
},
resolve: {
extensions: ['.js', '.jsx', '.ts', '.tsx'],
modules: [
path.resolve(__dirname, 'src'),
'node_modules'
],
alias: {
'@': path.resolve(__dirname, 'src'),
react: 'preact/compat',
'react-dom': 'preact/compat'
}
},
module: {
rules: [
// JavaScript/TypeScript with advanced caching
{
test: /\.(js|jsx|ts|tsx)$/,
include: path.resolve(__dirname, 'src'),
exclude: /node_modules/,
use: [
{
loader: 'thread-loader',
options: {
workers: require('os').cpus().length - 1,
poolTimeout: 2000
}
},
{
loader: 'babel-loader',
options: {
cacheDirectory: true,
cacheCompression: false,
compact: true,
presets: [
['@babel/preset-env', {
targets: {
browsers: ['> 1%', 'last 2 versions', 'not dead']
},
modules: false,
useBuiltIns: 'usage',
corejs: 3
}]
]
}
}
]
},
// CSS optimization
{
test: /\.css$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
options: {
esModule: false,
publicPath: (resourcePath) => {
return path.relative(resourcePath, path.resolve(__dirname, 'src'));
}
}
},
{
loader: 'css-loader',
options: {
importLoaders: 1,
sourceMap: false,
modules: {
auto: (resourcePath) => resourcePath.includes('.module.'),
localIdentName: '[name]__[local]--[hash:base64:5]'
}
}
},
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
require('autoprefixer'),
require('cssnano')({
preset: 'advanced'
})
]
}
}
}
]
},
// Image optimization
{
test: /\.(png|jpe?g|gif|webp)$/i,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 4 * 1024 // 4kb
}
},
generator: {
filename: 'images/[name].[contenthash:8][ext]'
},
use: [
{
loader: 'image-webpack-loader',
options: {
mozjpeg: { progressive: true, quality: 80 },
optipng: { enabled: false },
pngquant: { quality: [0.65, 0.90], speed: 4 },
gifsicle: { interlaced: false }
}
}
]
},
// SVG optimization
{
test: /\.svg$/,
use: [
{
loader: 'svg-url-loader',
options: {
limit: 10 * 1024,
noquotes: true
}
}
]
}
]
},
// Advanced optimization
optimization: {
minimize: true,
minimizer: [
// Custom Terser configuration
new TerserPlugin({
parallel: true,
terserOptions: {
compress: {
drop_console: true,
drop_debugger: true,
pure_funcs: ['console.log', 'console.info'],
passes: 3
},
mangle: {
safari10: true
},
format: {
comments: false,
quote_style: 3
}
},
extractComments: false
}),
// CSS optimization
new CssMinimizerPlugin({
minimizerOptions: {
preset: [
'default',
{
discardComments: { removeAll: true },
normalizeWhitespace: false,
colormin: true
}
]
}
})
],
// Advanced code splitting
splitChunks: {
chunks: 'all',
minSize: 20000,
maxSize: 244000,
minChunks: 1,
maxAsyncRequests: 30,
maxInitialRequests: 30,
automaticNameDelimiter: '~',
enforceSizeThreshold: 50000,
cacheGroups: {
// Frameworks
frameworks: {
test: /[\\/]node_modules[\\/](react|react-dom|preact)[\\/]/,
name: 'frameworks',
priority: 40,
enforce: true
},
// Libraries
libs: {
test: /[\\/]node_modules[\\/]/,
name(module) {
const packageName = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1];
return `npm.${packageName.replace('@', '')}`;
},
priority: 30,
minChunks: 1,
reuseExistingChunk: true
},
// Common modules
common: {
name: 'common',
minChunks: 2,
priority: 20,
chunks: 'initial',
reuseExistingChunk: true
},
// CSS
styles: {
test: /\.(css|scss|sass)$/,
name: 'styles',
priority: 10,
chunks: 'all',
enforce: true
}
}
},
// Runtime optimization
runtimeChunk: {
name: (entrypoint) => `runtime~${entrypoint.name}`
},
// Module concatenation (scope hoisting)
concatenateModules: true,
// Side effects optimization
sideEffects: false,
// Export optimization
usedExports: true,
// Provided exports for tree shaking
providedExports: true
},
// Advanced plugins
plugins: [
// Environment variables with optimizations
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('production'),
'process.env.GENERATE_SOURCEMAP': JSON.stringify(false),
'process.env.PUBLIC_URL': JSON.stringify(''),
__DEV__: false,
__PROD__: true
}),
// CSS extraction with optimizations
new MiniCssExtractPlugin({
filename: 'css/[name].[contenthash:8].css',
chunkFilename: 'css/[name].[contenthash:8].chunk.css',
ignoreOrder: true
}),
// Tree shake CSS
new PurgeCSSPlugin({
paths: glob.sync(`${path.join(__dirname, 'src')}/**/*`, { nodir: true }),
defaultExtractor: (content) => content.match(/[\w-/:]+(?<!:)/g) || [],
safelist: {
standard: [/-(leave|enter|appear)(|-(to|from|active))$/],
deep: [/^modal-/, /^tooltip-/, /^popover-/]
}
}),
// Gzip compression
new CompressionPlugin({
algorithm: 'gzip',
test: /\.(js|css|html|svg)$/,
threshold: 10240,
minRatio: 0.8,
compressionOptions: {
level: 9
}
}),
// Brotli compression
new CompressionPlugin({
algorithm: 'brotliCompress',
test: /\.(js|css|html|svg)$/,
threshold: 10240,
minRatio: 0.8,
compressionOptions: {
level: 11
},
filename: '[path][base].br'
}),
// Hard source caching for faster builds
new HardSourceWebpackPlugin({
cacheDirectory: path.join(__dirname, '.webpack-cache'),
environmentHash: {
root: process.cwd(),
directories: [],
files: ['package.json', 'yarn.lock']
}
}),
// Bundle analyzer for analysis
new BundleAnalyzerPlugin({
analyzerMode: 'static',
openAnalyzer: false,
reportFilename: path.resolve(__dirname, 'dist/bundle-report.html')
}),
// Replace development modules with production equivalents
new NormalModuleReplacementPlugin(
/^react-refresh$/,
'react-refresh/cjs/react-refresh-babel.development.js'
),
// Progress bar
new webpack.ProgressPlugin()
],
// Performance hints
performance: {
hints: 'warning',
maxEntrypointSize: 250000,
maxAssetSize: 250000,
assetFilter: (assetFilename) => {
return !assetFilename.includes('\.map');
}
},
// Source maps
devtool: false,
// Caching
cache: {
type: 'filesystem',
buildDependencies: {
config: [__filename]
}
},
// Externals for CDN usage
externals: {
// react: 'React',
// 'react-dom': 'ReactDOM',
// lodash: '_'
},
// Advanced stats
stats: {
preset: 'normal',
builtAt: true,
moduleTrace: true,
source: false,
errorDetails: true,
colors: true,
modules: false,
children: false,
chunks: false,
chunkModules: false,
entrypoints: false,
performance: true,
hash: true,
version: true,
timings: true
}
});
// webpack-dev.config.js (Development optimized)
module.exports = {
mode: 'development',
entry: {
main: './src/index.js'
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].js',
publicPath: '/'
},
devtool: 'eval-cheap-module-source-map',
devServer: {
contentBase: path.join(__dirname, 'dist'),
hot: true,
compress: true,
port: 3000,
historyApiFallback: true,
overlay: {
errors: true,
warnings: true
},
watchOptions: {
aggregateTimeout: 300,
poll: 1000,
ignored: /node_modules/
},
client: {
logging: 'info',
overlay: {
errors: true,
warnings: false
}
}
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('development'),
__DEV__: true,
__PROD__: false
})
],
module: {
rules: [
{
test: /\.(js|jsx|ts|tsx)$/,
use: 'babel-loader',
exclude: /node_modules/
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
}
]
},
resolve: {
extensions: ['.js', '.jsx', '.ts', '.tsx'],
alias: {
'@': path.resolve(__dirname, 'src')
}
}
};
// webpack.config.js (Main configuration that merges based on environment)
const { merge } = require('webpack-merge');
const commonConfig = require('./webpack.common.js');
module.exports = (env, argv) => {
const isProduction = argv.mode === 'production';
const envConfig = isProduction
? require('./webpack.prod.js')
: require('./webpack.dev.js');
return merge(commonConfig, envConfig);
};
// Package.json scripts for advanced optimization
{
"scripts": {
"dev": "webpack serve --config webpack.dev.js",
"build": "webpack --config webpack.prod.js",
"build:analyze": "npm run build && open dist/bundle-report.html",
"build:profile": "webpack --config webpack.prod.js --profile",
"clean": "rimraf dist .webpack-cache",
"size-limit": "size-limit",
"lighthouse": "lighthouse http://localhost:3000 --output=html --output-path=./lighthouse-report.html"
}
}
// .size-limit.json for bundle size monitoring
[
{
"path": "dist/js/main.*.js",
"limit": "50 KB"
},
{
"path": "dist/css/main.*.css",
"limit": "20 KB"
},
{
"path": "dist/js/vendor.*.js",
"limit": "100 KB"
}
]
💻 Webpack 加载器和插件 javascript
🟡 intermediate
⭐⭐⭐⭐
自定义webpack加载器、插件开发和集成示例
⏱️ 35 min
🏷️ webpack, loaders, plugins, development
Prerequisites:
Webpack basics, Node.js streams, Plugin architecture
// Webpack Custom Loaders and Plugins Examples
// ===== CUSTOM LOADER EXAMPLES =====
// 1. Markdown to HTML Loader
// loaders/markdown-loader.js
const marked = require('marked');
const highlight = require('highlight.js');
module.exports = function(source) {
// Configure marked with syntax highlighting
marked.setOptions({
highlight: (code, lang) => {
if (lang && highlight.getLanguage(lang)) {
return highlight.highlight(code, { language: lang }).value;
}
return highlight.highlightAuto(code).value;
},
breaks: true,
gfm: true
});
// Convert markdown to HTML
const html = marked(source);
// Return as a JavaScript module
return `export default ${JSON.stringify(html)};`;
};
// 2. Image Optimization Loader
// loaders/image-optimizer-loader.js
const sharp = require('sharp');
const path = require('path');
const crypto = require('crypto');
module.exports = function(buffer) {
const callback = this.async();
const { width, height, quality = 80, format = 'webp' } = this.getOptions();
// Generate hash for caching
const hash = crypto
.createHash('md5')
.update(buffer + JSON.stringify({ width, height, quality, format }))
.digest('hex');
const outputPath = path.join(
this.outputPath,
'optimized-images',
`${this.resourcePath.split('/').pop().split('.')[0]}-${hash}.${format}`
);
// Process image with sharp
const transformer = sharp(buffer);
if (width || height) {
transformer.resize(width, height, { fit: 'inside', withoutEnlargement: true });
}
transformer
.toFormat(format, { quality })
.toBuffer()
.then(outputBuffer => {
this.emitFile(outputPath, outputBuffer);
callback(null, `export default "${outputPath}";`);
})
.catch(err => callback(err));
};
module.exports.raw = true;
// 3. Translation Loader
// loaders/i18n-loader.js
const fs = require('fs');
const path = require('path');
module.exports = function(source) {
const content = JSON.parse(source);
const translations = {};
// Scan translation files
const localesDir = path.resolve(this.context, 'locales');
if (fs.existsSync(localesDir)) {
const locales = fs.readdirSync(localesDir);
locales.forEach(locale => {
const localePath = path.join(localesDir, locale);
if (fs.statSync(localePath).isDirectory()) {
const filePath = path.join(localePath, `${path.basename(this.resource, '.json')}.json`);
if (fs.existsSync(filePath)) {
translations[locale] = JSON.parse(fs.readFileSync(filePath, 'utf8'));
}
}
});
}
// Generate translation functions
const translationsCode = Object.keys(translations)
.map(locale => {
const localeTranslations = translations[locale];
const translationsMap = JSON.stringify(localeTranslations, null, 2);
return `const ${locale} = ${translationsMap};`;
})
.join('\n');
return `
// Default translations from the JSON file
const defaultTranslations = ${JSON.stringify(content, null, 2)};
// Locale-specific translations
${translationsCode}
const translations = {
en: defaultTranslations,
${Object.keys(translations).map(locale => `${locale}: ${locale}`).join(',\n ')}
};
export function t(key, locale = 'en') {
const keys = key.split('.');
let translation = translations[locale];
for (const k of keys) {
if (translation && translation[k]) {
translation = translation[k];
} else {
return key; // Fallback to key if translation not found
}
}
return translation || key;
}
export default translations;
`;
};
// 4. Environment Variable Loader
// loaders/env-loader.js
const fs = require('fs');
const path = require('path');
module.exports = function(source) {
const envFile = path.resolve(this.context, '.env');
let envVars = {};
if (fs.existsSync(envFile)) {
const envContent = fs.readFileSync(envFile, 'utf8');
envContent.split('\n').forEach(line => {
const match = line.match(/^([^=]+)=(.*)$/);
if (match) {
envVars[match[1]] = match[2];
}
});
}
const processEnv = { ...process.env, ...envVars };
const envRegex = /process\.env\.([A-Z_]+)/g;
let result = source.replace(envRegex, (match, envVar) => {
const value = processEnv[envVar];
return value ? JSON.stringify(value) : 'undefined';
});
return result;
};
// ===== CUSTOM PLUGIN EXAMPLES =====
// 1. Asset Versioning Plugin
// plugins/asset-version-plugin.js
const crypto = require('crypto');
const path = require('path');
const fs = require('fs');
class AssetVersionPlugin {
constructor(options = {}) {
this.options = {
manifestPath: options.manifestPath || 'asset-manifest.json',
versionKey: options.versionKey || 'version',
...options
};
}
apply(compiler) {
compiler.hooks.emit.tapAsync('AssetVersionPlugin', (compilation, callback) => {
const manifest = {};
const version = Date.now() + '-' + crypto.randomBytes(4).toString('hex');
// Process all assets
for (const [filename, asset] of Object.entries(compilation.assets)) {
const content = asset.source();
const hash = crypto.createHash('md5').update(content).digest('hex');
manifest[filename] = {
version: hash,
url: filename,
[this.options.versionKey]: version
};
}
// Write manifest file
const manifestJson = JSON.stringify(manifest, null, 2);
compilation.assets[this.options.manifestPath] = {
source: () => manifestJson,
size: () => manifestJson.length
};
callback();
});
}
}
module.exports = AssetVersionPlugin;
// 2. Bundle Size Analyzer Plugin
// plugins/bundle-size-analyzer-plugin.js
const fs = require('fs');
const path = require('path');
class BundleSizeAnalyzerPlugin {
constructor(options = {}) {
this.options = {
outputPath: options.outputPath || 'bundle-size-report.json',
thresholds: options.thresholds || {
total: 1024 * 1024, // 1MB
chunks: 1024 * 256 // 256KB per chunk
}
};
}
apply(compiler) {
compiler.hooks.afterEmit.tap('BundleSizeAnalyzerPlugin', (compilation) => {
const report = {
timestamp: new Date().toISOString(),
totalSize: 0,
chunks: {},
assets: {},
warnings: [],
errors: []
};
// Analyze chunks
for (const [name, chunk] of compilation.chunks.entries()) {
const size = chunk.files.reduce((total, file) => {
return total + compilation.assets[file].size();
}, 0);
report.chunks[name] = {
size,
files: chunk.files,
modules: chunk.modules.map(m => ({
identifier: m.identifier(),
size: m.size()
}))
};
report.totalSize += size;
// Check thresholds
if (size > this.options.thresholds.chunks) {
report.warnings.push(`Chunk "${name}" exceeds size threshold: ${(size / 1024).toFixed(2)}KB`);
}
}
// Check total size threshold
if (report.totalSize > this.options.thresholds.total) {
report.errors.push(`Total bundle size exceeds threshold: ${(report.totalSize / 1024 / 1024).toFixed(2)}MB`);
}
// Analyze individual assets
for (const [name, asset] of Object.entries(compilation.assets)) {
report.assets[name] = {
size: asset.size()
};
}
// Write report
const reportPath = path.join(compilation.outputOptions.path, this.options.outputPath);
fs.writeFileSync(reportPath, JSON.stringify(report, null, 2));
// Log warnings and errors
if (report.warnings.length > 0) {
console.warn('Bundle Size Warnings:');
report.warnings.forEach(warning => console.warn(` - ${warning}`));
}
if (report.errors.length > 0) {
console.error('Bundle Size Errors:');
report.errors.forEach(error => console.error(` - ${error}`));
}
});
}
}
module.exports = BundleSizeAnalyzerPlugin;
// 3. Component Documentation Generator Plugin
// plugins/component-docs-plugin.js
const fs = require('fs');
const path = require('path');
const parseJsdoc = require('jsdoc-to-markdown');
class ComponentDocsPlugin {
constructor(options = {}) {
this.options = {
componentsPath: options.componentsPath || 'src/components',
outputDir: options.outputDir || 'docs/components',
...options
};
}
apply(compiler) {
compiler.hooks.beforeCompile.tap('ComponentDocsPlugin', () => {
const componentsPath = path.resolve(compiler.context, this.options.componentsPath);
const outputDir = path.resolve(compiler.context, this.options.outputDir);
// Ensure output directory exists
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true });
}
// Find all component files
const componentFiles = this.findComponentFiles(componentsPath);
// Generate documentation for each component
componentFiles.forEach(filePath => {
const componentName = path.basename(filePath, path.extname(filePath));
const documentation = this.generateComponentDocumentation(filePath);
const outputPath = path.join(outputDir, `${componentName}.md`);
fs.writeFileSync(outputPath, documentation);
});
// Generate index file
this.generateIndexDoc(componentFiles, outputDir);
});
}
findComponentFiles(dir) {
const files = [];
function traverse(currentDir) {
const items = fs.readdirSync(currentDir);
for (const item of items) {
const fullPath = path.join(currentDir, item);
const stat = fs.statSync(fullPath);
if (stat.isDirectory()) {
traverse(fullPath);
} else if (path.extname(item) === '.js' || path.extname(item) === '.jsx') {
files.push(fullPath);
}
}
}
traverse(dir);
return files;
}
generateComponentDocumentation(filePath) {
const source = fs.readFileSync(filePath, 'utf8');
const componentName = path.basename(filePath, path.extname(filePath));
// Extract JSDoc comments
const jsdoc = parseJsdoc.getTemplateData(source);
// Generate markdown
return `
# ${componentName}
## Description
${jsdoc.description || 'No description available.'}
## Props
${jsdoc.params ? jsdoc.params.map(param =>
`- **${param.name}** (${param.type.names.join('|')}) - ${param.description}`
).join('\n') : 'No props documented.'}
## Examples
```jsx
import ${componentName} from './${componentName}';
function App() {
return <${componentName} />;
}
```
## Source
[${path.basename(filePath)}](../${path.relative(process.cwd(), filePath)})
`;
}
generateIndexDoc(files, outputDir) {
const indexPath = path.join(outputDir, 'README.md');
const content = `
# Component Documentation
This directory contains documentation for all components in the application.
## Components
${files.map(file => {
const componentName = path.basename(file, path.extname(file));
const relativePath = path.relative(outputDir, file);
return `- [${componentName}](${componentName}.md)`;
}).join('\n')}
## Generated On
${new Date().toISOString()}
`;
fs.writeFileSync(indexPath, content);
}
}
module.exports = ComponentDocsPlugin;
// ===== INTEGRATION EXAMPLES =====
// webpack.config.js with custom loaders and plugins
const path = require('path');
const { AssetVersionPlugin } = require('./plugins/asset-version-plugin');
const { BundleSizeAnalyzerPlugin } = require('./plugins/bundle-size-analyzer-plugin');
const { ComponentDocsPlugin } = require('./plugins/component-docs-plugin');
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[contenthash].js'
},
module: {
rules: [
// Custom markdown loader
{
test: /\.md$/,
use: [
'html-loader',
path.resolve(__dirname, 'loaders/markdown-loader.js')
]
},
// Custom image optimizer loader
{
test: /\.(png|jpe?g|gif|svg)$/,
oneOf: [
{
resourceQuery: /optimize/,
use: path.resolve(__dirname, 'loaders/image-optimizer-loader.js')
},
{
type: 'asset/resource'
}
]
},
// Custom i18n loader
{
test: /\.translation\.json$/,
type: 'javascript/auto',
use: path.resolve(__dirname, 'loaders/i18n-loader.js')
},
// Custom env loader
{
test: /\.env\.(js|mjs)$/,
use: path.resolve(__dirname, 'loaders/env-loader.js')
}
]
},
plugins: [
new AssetVersionPlugin({
manifestPath: 'asset-manifest.json'
}),
new BundleSizeAnalyzerPlugin({
outputPath: 'bundle-analysis.json',
thresholds: {
total: 1024 * 512, // 512KB
chunks: 1024 * 128 // 128KB
}
}),
new ComponentDocsPlugin({
componentsPath: 'src/components',
outputDir: 'docs/components'
})
],
resolveLoader: {
alias: {
'markdown-loader': path.resolve(__dirname, 'loaders/markdown-loader.js'),
'image-optimizer-loader': path.resolve(__dirname, 'loaders/image-optimizer-loader.js'),
'i18n-loader': path.resolve(__dirname, 'loaders/i18n-loader.js'),
'env-loader': path.resolve(__dirname, 'loaders/env-loader.js')
}
}
};
// Example usage in components
import React, { useState } from 'react';
import './styles.css';
// Using custom image optimizer
import optimizedImage from './image.png?optimize';
// Using custom i18n loader
import { t } from './translation.json';
const MyComponent = () => {
const [message] = useState(t('welcome.message'));
return (
<div>
<h1>{message}</h1>
<img src={optimizedImage} alt="Optimized image" />
</div>
);
};
export default MyComponent;