🎯 Exemples recommandés
Balanced sample collections from various categories for you to explore
Exemples de Configuration Webpack
Exemples de configuration Webpack incluant la configuration de base, l'optimisation, les loaders, les plugins et les patterns Webpack modernes
💻 Configuration de Base Webpack javascript
🟢 simple
⭐⭐
Configuration essentielle de Webpack pour les applications JavaScript avec environnements de développement et de production
⏱️ 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')
]
}
💻 Optimisation Avancée Webpack javascript
🟡 intermediate
⭐⭐⭐⭐
Optimisation des performances, division du code, tree shaking et fonctionnalités avancées de 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"
}
]
💻 Loaders et Plugins Webpack javascript
🟡 intermediate
⭐⭐⭐⭐
Développement de loaders et plugins personnalisés Webpack et exemples d'intégration
⏱️ 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;