JavaScript Build Tools: Webpack, Vite, Rollup Complete Guide
Master modern JavaScript build tools. Learn Webpack, Vite, Rollup, esbuild configuration, optimization, and build pipeline best practices.
Modern JavaScript development relies heavily on build tools to bundle, optimize, and transform code. This comprehensive guide covers popular build tools including Webpack, Vite, Rollup, and esbuild with practical configurations and optimization techniques.
Why Build Tools Matter
Build tools serve several critical purposes in modern JavaScript development:
// Before build tools - manual script inclusion
<!DOCTYPE html>
<html>
<head>
<script src="lodash.js"></script>
<script src="jquery.js"></script>
<script src="utils.js"></script>
<script src="components.js"></script>
<script src="app.js"></script>
</head>
</html>
// With build tools - single optimized bundle
<!DOCTYPE html>
<html>
<head>
<script src="bundle.min.js"></script>
</head>
</html>
// Modern module syntax
import { debounce } from 'lodash';
import $ from 'jquery';
import { fetchUser } from './utils';
import Header from './components/Header';
// Build tools handle:
// - Module bundling
// - Code transformation (TypeScript, JSX)
// - Optimization (minification, tree shaking)
// - Asset processing (images, CSS)
// - Development server
// - Hot module replacement
Webpack
Basic Webpack Configuration
// webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
module.exports = (env, argv) => {
const isProduction = argv.mode === 'production';
return {
entry: {
main: './src/index.js',
vendor: ['react', 'react-dom']
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: isProduction ? '[name].[contenthash].js' : '[name].js',
chunkFilename: isProduction ? '[name].[contenthash].chunk.js' : '[name].chunk.js',
clean: true,
publicPath: '/'
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: [
['@babel/preset-env', {
useBuiltIns: 'usage',
corejs: 3
}],
'@babel/preset-react'
],
plugins: [
'@babel/plugin-proposal-class-properties',
isProduction && 'babel-plugin-transform-react-remove-prop-types'
].filter(Boolean)
}
}
},
{
test: /\.css$/,
use: [
isProduction ? MiniCssExtractPlugin.loader : 'style-loader',
{
loader: 'css-loader',
options: {
modules: {
localIdentName: isProduction ? '[hash:base64]' : '[name]__[local]__[hash:base64:5]'
}
}
},
'postcss-loader'
]
},
{
test: /\.scss$/,
use: [
isProduction ? MiniCssExtractPlugin.loader : 'style-loader',
'css-loader',
'postcss-loader',
'sass-loader'
]
},
{
test: /\.(png|jpe?g|gif|svg)$/,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 8 * 1024 // 8kb
}
},
generator: {
filename: 'images/[name].[hash][ext]'
}
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
type: 'asset/resource',
generator: {
filename: 'fonts/[name].[hash][ext]'
}
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: './public/index.html',
minify: isProduction ? {
removeComments: true,
collapseWhitespace: true,
removeRedundantAttributes: true,
removeScriptTypeAttributes: true,
removeStyleLinkTypeAttributes: true,
minifyJS: true,
minifyCSS: true
} : false
}),
isProduction && new MiniCssExtractPlugin({
filename: '[name].[contenthash].css',
chunkFilename: '[name].[contenthash].chunk.css'
})
].filter(Boolean),
optimization: {
minimizer: isProduction ? [
new TerserPlugin({
terserOptions: {
compress: {
drop_console: true
}
}
}),
new OptimizeCSSAssetsPlugin()
] : [],
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
priority: 10,
enforce: true
},
common: {
minChunks: 2,
priority: 5,
reuseExistingChunk: true
}
}
},
runtimeChunk: {
name: 'runtime'
}
},
resolve: {
extensions: ['.js', '.jsx', '.ts', '.tsx'],
alias: {
'@': path.resolve(__dirname, 'src'),
'@components': path.resolve(__dirname, 'src/components'),
'@utils': path.resolve(__dirname, 'src/utils'),
'@assets': path.resolve(__dirname, 'src/assets')
}
},
devServer: {
contentBase: path.join(__dirname, 'dist'),
historyApiFallback: true,
hot: true,
port: 3000,
open: true,
overlay: {
warnings: false,
errors: true
}
},
devtool: isProduction ? 'source-map' : 'eval-source-map'
};
};
// package.json scripts
{
"scripts": {
"dev": "webpack serve --mode development",
"build": "webpack --mode production",
"analyze": "webpack-bundle-analyzer dist/static/js/*.js"
}
}
Advanced Webpack Features
// webpack.config.js - Advanced configuration
const webpack = require('webpack');
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
const CompressionPlugin = require('compression-webpack-plugin');
const WorkboxPlugin = require('workbox-webpack-plugin');
// Custom plugin for build timing
class BuildTimePlugin {
apply(compiler) {
compiler.hooks.run.tap('BuildTimePlugin', () => {
console.log('Build started at:', new Date().toLocaleTimeString());
});
compiler.hooks.done.tap('BuildTimePlugin', (stats) => {
console.log('Build completed at:', new Date().toLocaleTimeString());
console.log('Build duration:', stats.endTime - stats.startTime, 'ms');
});
}
}
module.exports = {
// ... previous config
plugins: [
// Previous plugins...
// Environment variables
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
'process.env.API_URL': JSON.stringify(
process.env.API_URL || 'https://api.example.com'
),
__DEV__: process.env.NODE_ENV === 'development',
}),
// Bundle analysis
process.env.ANALYZE &&
new BundleAnalyzerPlugin({
analyzerMode: 'server',
openAnalyzer: true,
}),
// Compression
new CompressionPlugin({
algorithm: 'gzip',
test: /\.(js|css|html|svg)$/,
threshold: 8192,
minRatio: 0.8,
}),
// Service Worker
new WorkboxPlugin.GenerateSW({
clientsClaim: true,
skipWaiting: true,
runtimeCaching: [
{
urlPattern: /^https:\/\/api\.example\.com/,
handler: 'NetworkFirst',
options: {
cacheName: 'api-cache',
expiration: {
maxEntries: 50,
maxAgeSeconds: 300,
},
},
},
],
}),
// Custom plugin
new BuildTimePlugin(),
].filter(Boolean),
// Performance budgets
performance: {
maxAssetSize: 250000,
maxEntrypointSize: 250000,
hints: process.env.NODE_ENV === 'production' ? 'warning' : false,
},
// Module federation (micro-frontends)
plugins: [
new webpack.container.ModuleFederationPlugin({
name: 'shell',
remotes: {
mfe1: 'mfe1@http://localhost:3001/remoteEntry.js',
mfe2: 'mfe2@http://localhost:3002/remoteEntry.js',
},
shared: {
react: { singleton: true },
'react-dom': { singleton: true },
},
}),
],
};
// webpack.dev.js - Development specific config
const { merge } = require('webpack-merge');
const common = require('./webpack.common.js');
module.exports = merge(common, {
mode: 'development',
devtool: 'eval-source-map',
devServer: {
hot: true,
port: 3000,
historyApiFallback: true,
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
secure: false,
},
},
},
optimization: {
removeAvailableModules: false,
removeEmptyChunks: false,
splitChunks: false,
},
});
// webpack.prod.js - Production specific config
const { merge } = require('webpack-merge');
const common = require('./webpack.common.js');
module.exports = merge(common, {
mode: 'production',
devtool: 'source-map',
optimization: {
minimize: true,
sideEffects: false,
concatenateModules: true,
splitChunks: {
chunks: 'all',
minSize: 20000,
maxSize: 244000,
cacheGroups: {
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true,
},
vendor: {
test: /[\\/]node_modules[\\/]/,
priority: -10,
chunks: 'initial',
},
},
},
},
});
Vite
Vite Configuration
// vite.config.js
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { resolve } from 'path';
import { visualizer } from 'rollup-plugin-visualizer';
export default defineConfig(({ command, mode }) => {
const isProduction = mode === 'production';
return {
plugins: [
react(),
// Bundle analyzer
isProduction && visualizer({
filename: 'dist/stats.html',
open: true
})
].filter(Boolean),
// Development server
server: {
port: 3000,
open: true,
cors: true,
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
secure: false
}
}
},
// Build configuration
build: {
outDir: 'dist',
sourcemap: true,
minify: 'terser',
terserOptions: {
compress: {
drop_console: isProduction,
drop_debugger: isProduction
}
},
rollupOptions: {
output: {
manualChunks: {
vendor: ['react', 'react-dom'],
utils: ['lodash', 'date-fns']
}
}
},
chunkSizeWarningLimit: 1000
},
// Path resolution
resolve: {
alias: {
'@': resolve(__dirname, 'src'),
'@components': resolve(__dirname, 'src/components'),
'@utils': resolve(__dirname, 'src/utils'),
'@assets': resolve(__dirname, 'src/assets')
}
},
// CSS configuration
css: {
modules: {
localsConvention: 'camelCase'
},
preprocessorOptions: {
scss: {
additionalData: `@import "@/styles/variables.scss";`
}
}
},
// Environment variables
define: {
__APP_VERSION__: JSON.stringify(process.env.npm_package_version),
__API_URL__: JSON.stringify(process.env.VITE_API_URL || 'https://api.example.com')
},
// Optimization
optimizeDeps: {
include: ['react', 'react-dom'],
exclude: ['some-large-dependency']
}
};
});
// Custom Vite plugin
function customPlugin() {
return {
name: 'custom-plugin',
buildStart() {
console.log('Build started with Vite');
},
generateBundle(options, bundle) {
// Custom bundle manipulation
console.log('Generated bundle with', Object.keys(bundle).length, 'chunks');
},
writeBundle() {
console.log('Bundle written to disk');
}
};
}
// Environment-specific configs
// vite.config.dev.js
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
server: {
hmr: {
overlay: false
}
},
build: {
sourcemap: true,
minify: false
}
});
// vite.config.prod.js
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { resolve } from 'path';
export default defineConfig({
plugins: [react()],
build: {
minify: 'terser',
rollupOptions: {
input: {
main: resolve(__dirname, 'index.html'),
admin: resolve(__dirname, 'admin.html')
}
}
}
});
Vite Plugin Development
// plugins/vite-plugin-custom.js
import { createFilter } from '@rollup/pluginutils';
import MagicString from 'magic-string';
export function customTransformPlugin(options = {}) {
const filter = createFilter(options.include, options.exclude);
return {
name: 'custom-transform',
transform(code, id) {
if (!filter(id)) return null;
// Custom code transformation
const magicString = new MagicString(code);
// Example: Replace console.log with custom logger
if (options.replaceConsole) {
magicString.replace(/console\.log/g, 'customLogger');
}
// Example: Add development helpers
if (options.addHelpers && process.env.NODE_ENV === 'development') {
magicString.prepend('// Development helpers injected\n');
}
return {
code: magicString.toString(),
map: magicString.generateMap({ hires: true }),
};
},
generateBundle(options, bundle) {
// Post-process bundle
Object.keys(bundle).forEach((fileName) => {
const chunk = bundle[fileName];
if (chunk.type === 'chunk') {
console.log(
`Generated chunk: ${fileName} (${chunk.code.length} bytes)`
);
}
});
},
};
}
// Usage in vite.config.js
import { customTransformPlugin } from './plugins/vite-plugin-custom.js';
export default defineConfig({
plugins: [
react(),
customTransformPlugin({
include: ['src/**/*.js', 'src/**/*.jsx'],
replaceConsole: true,
addHelpers: true,
}),
],
});
Rollup
Rollup Configuration
// rollup.config.js
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import babel from '@rollup/plugin-babel';
import terser from '@rollup/plugin-terser';
import postcss from 'rollup-plugin-postcss';
import { visualizer } from 'rollup-plugin-visualizer';
import copy from 'rollup-plugin-copy';
const isProduction = process.env.NODE_ENV === 'production';
export default [
// Main application bundle
{
input: 'src/index.js',
output: [
{
file: 'dist/bundle.cjs.js',
format: 'cjs',
sourcemap: true
},
{
file: 'dist/bundle.esm.js',
format: 'esm',
sourcemap: true
},
{
file: 'dist/bundle.umd.js',
format: 'umd',
name: 'MyLibrary',
sourcemap: true,
globals: {
react: 'React',
'react-dom': 'ReactDOM'
}
}
],
plugins: [
resolve({
browser: true,
preferBuiltins: false
}),
commonjs(),
babel({
babelHelpers: 'bundled',
exclude: 'node_modules/**',
presets: [
['@babel/preset-env', {
modules: false,
targets: {
browsers: ['> 1%', 'last 2 versions']
}
}],
'@babel/preset-react'
]
}),
postcss({
extract: 'styles.css',
minimize: isProduction,
sourceMap: true
}),
isProduction && terser({
compress: {
drop_console: true
}
}),
copy({
targets: [
{ src: 'public/*', dest: 'dist' }
]
}),
visualizer({
filename: 'dist/bundle-analysis.html',
open: true
})
].filter(Boolean),
external: ['react', 'react-dom'],
watch: {
include: 'src/**',
clearScreen: false
}
},
// Library bundle
{
input: 'src/lib/index.js',
output: {
file: 'dist/lib.js',
format: 'esm',
sourcemap: true
},
plugins: [
resolve(),
babel({
babelHelpers: 'bundled',
exclude: 'node_modules/**'
}),
isProduction && terser()
].filter(Boolean),
external: id => !id.startsWith('.') && !id.startsWith('/')
}
];
// rollup.config.dev.js - Development configuration
import serve from 'rollup-plugin-serve';
import livereload from 'rollup-plugin-livereload';
export default {
input: 'src/index.js',
output: {
file: 'dist/bundle.js',
format: 'iife',
sourcemap: true
},
plugins: [
resolve({ browser: true }),
commonjs(),
babel({ babelHelpers: 'bundled' }),
serve({
contentBase: 'dist',
port: 3000,
open: true
}),
livereload('dist')
]
};
Custom Rollup Plugins
// plugins/rollup-plugin-environment.js
export function environmentPlugin(options = {}) {
return {
name: 'environment',
buildStart() {
console.log(
`Building for ${process.env.NODE_ENV || 'development'} environment`
);
},
transform(code, id) {
// Replace environment variables
let transformedCode = code;
Object.keys(process.env).forEach((key) => {
if (key.startsWith('APP_')) {
transformedCode = transformedCode.replace(
new RegExp(`process\\.env\\.${key}`, 'g'),
JSON.stringify(process.env[key])
);
}
});
return transformedCode !== code ? { code: transformedCode } : null;
},
};
}
// plugins/rollup-plugin-asset-size.js
export function assetSizePlugin(options = {}) {
const { threshold = 100000 } = options; // 100KB default
return {
name: 'asset-size',
generateBundle(outputOptions, bundle) {
Object.keys(bundle).forEach((fileName) => {
const asset = bundle[fileName];
if (asset.type === 'chunk') {
const size = Buffer.byteLength(asset.code, 'utf8');
if (size > threshold) {
this.warn(
`Large chunk detected: ${fileName} (${(size / 1000).toFixed(1)}KB)`
);
}
console.log(`${fileName}: ${(size / 1000).toFixed(1)}KB`);
}
});
},
};
}
// Usage
import { environmentPlugin, assetSizePlugin } from './plugins';
export default {
// ... config
plugins: [environmentPlugin(), assetSizePlugin({ threshold: 50000 })],
};
esbuild
esbuild Configuration
// build.js - esbuild script
const esbuild = require('esbuild');
const { sassPlugin } = require('esbuild-sass-plugin');
const { copy } = require('esbuild-plugin-copy');
const isProduction = process.env.NODE_ENV === 'production';
// Basic build
async function build() {
try {
await esbuild.build({
entryPoints: ['src/index.js'],
bundle: true,
outfile: 'dist/bundle.js',
minify: isProduction,
sourcemap: true,
format: 'esm',
target: ['es2020'],
plugins: [
sassPlugin(),
copy({
assets: {
from: ['./public/*'],
to: ['./dist']
}
})
],
define: {
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
'process.env.API_URL': JSON.stringify(process.env.API_URL || 'https://api.example.com')
},
external: ['react', 'react-dom'],
loader: {
'.png': 'file',
'.jpg': 'file',
'.svg': 'text'
}
});
console.log('Build completed successfully');
} catch (error) {
console.error('Build failed:', error);
process.exit(1);
}
}
// Development server
async function serve() {
const ctx = await esbuild.context({
entryPoints: ['src/index.js'],
bundle: true,
outdir: 'dist',
sourcemap: true,
format: 'esm',
plugins: [sassPlugin()]
});
await ctx.watch();
const { host, port } = await ctx.serve({
servedir: 'dist',
port: 3000
});
console.log(`Server running at http://${host}:${port}`);
}
// Advanced esbuild configuration
const buildConfig = {
entryPoints: {
main: 'src/index.js',
admin: 'src/admin.js'
},
bundle: true,
outdir: 'dist',
splitting: true,
format: 'esm',
minify: isProduction,
keepNames: !isProduction,
sourcemap: true,
metafile: true,
plugins: [
{
name: 'build-timer',
setup(build) {
let startTime;
build.onStart(() => {
startTime = Date.now();
console.log('Build started...');
});
build.onEnd(result => {
const duration = Date.now() - startTime;
console.log(`Build completed in ${duration}ms`);
if (result.metafile) {
console.log('Bundle analysis:', esbuild.analyzeMetafileSync(result.metafile));
}
});
}
}
],
loader: {
'.js': 'jsx',
'.ts': 'tsx'
},
alias: {
'@': './src',
'@components': './src/components'
}
};
if (process.argv.includes('--serve')) {
serve();
} else {
build();
}
// package.json scripts
{
"scripts": {
"dev": "node build.js --serve",
"build": "NODE_ENV=production node build.js",
"analyze": "esbuild-visualizer --metadata=meta.json --open"
}
}
Build Optimization Techniques
// webpack-optimization.js
class BuildOptimizer {
// Bundle splitting strategies
static getSplitChunksConfig() {
return {
chunks: 'all',
minSize: 20000,
maxSize: 244000,
cacheGroups: {
// Vendor libraries
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
priority: 10,
enforce: true,
},
// Common modules
common: {
minChunks: 2,
priority: 5,
reuseExistingChunk: true,
},
// React ecosystem
react: {
test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,
name: 'react',
priority: 20,
enforce: true,
},
// UI libraries
ui: {
test: /[\\/]node_modules[\\/](@mui|antd|bootstrap)[\\/]/,
name: 'ui',
priority: 15,
enforce: true,
},
},
};
}
// Tree shaking configuration
static getTreeShakingConfig() {
return {
optimization: {
usedExports: true,
sideEffects: false,
// Mark specific packages as side-effect free
sideEffects: ['*.css', '*.scss', './src/polyfills.js'],
},
resolve: {
// Enable tree shaking for these modules
mainFields: ['es2015', 'module', 'main'],
},
};
}
// Lazy loading implementation
static createLazyComponent(importFn, fallback = null) {
return React.lazy(() => {
return importFn().catch((error) => {
console.error('Lazy loading failed:', error);
// Return error boundary component
return {
default: () =>
React.createElement('div', null, 'Failed to load component'),
};
});
});
}
// Resource hints generation
static generateResourceHints(chunks) {
const hints = [];
chunks.forEach((chunk) => {
if (chunk.isInitial) {
hints.push(`<link rel="preload" href="${chunk.files[0]}" as="script">`);
} else {
hints.push(`<link rel="prefetch" href="${chunk.files[0]}">`);
}
});
return hints.join('\n');
}
// Performance monitoring
static monitorBuildPerformance(stats) {
const assets = stats.compilation.assets;
const chunks = stats.compilation.chunks;
console.log('=== Build Performance Report ===');
// Asset sizes
Object.keys(assets).forEach((name) => {
const size = assets[name].size();
if (size > 250000) {
// 250KB
console.warn(
`Large asset detected: ${name} (${(size / 1000).toFixed(1)}KB)`
);
}
});
// Chunk analysis
chunks.forEach((chunk) => {
const modules = chunk.getModules();
console.log(`Chunk ${chunk.name}: ${modules.length} modules`);
});
// Build timing
console.log(`Build time: ${stats.endTime - stats.startTime}ms`);
}
}
// Dynamic imports and code splitting
class ComponentLoader {
static cache = new Map();
static async loadComponent(name) {
if (this.cache.has(name)) {
return this.cache.get(name);
}
try {
let componentModule;
switch (name) {
case 'Dashboard':
componentModule = await import('./components/Dashboard');
break;
case 'Settings':
componentModule = await import('./components/Settings');
break;
case 'Reports':
componentModule = await import('./components/Reports');
break;
default:
throw new Error(`Unknown component: ${name}`);
}
const Component = componentModule.default;
this.cache.set(name, Component);
return Component;
} catch (error) {
console.error(`Failed to load component ${name}:`, error);
return null;
}
}
static preloadComponents(names) {
names.forEach((name) => {
this.loadComponent(name).catch(() => {
// Silently fail for preloading
});
});
}
}
// Progressive loading strategy
class ProgressiveLoader {
constructor() {
this.loadQueue = [];
this.loading = false;
}
async loadInBatches(modules, batchSize = 3) {
const batches = this.createBatches(modules, batchSize);
for (const batch of batches) {
await Promise.all(batch.map((module) => this.loadModule(module)));
// Small delay between batches to prevent overwhelming
await new Promise((resolve) => setTimeout(resolve, 100));
}
}
createBatches(array, size) {
const batches = [];
for (let i = 0; i < array.length; i += size) {
batches.push(array.slice(i, i + size));
}
return batches;
}
async loadModule(modulePath) {
try {
const module = await import(modulePath);
console.log(`Loaded module: ${modulePath}`);
return module;
} catch (error) {
console.error(`Failed to load module ${modulePath}:`, error);
return null;
}
}
}
Build Pipeline Automation
// build-pipeline.js
const fs = require('fs').promises;
const path = require('path');
const { execSync } = require('child_process');
class BuildPipeline {
constructor(config) {
this.config = {
buildDir: 'dist',
sourceDir: 'src',
...config,
};
}
async runPipeline() {
console.log('Starting build pipeline...');
try {
await this.preBuild();
await this.build();
await this.postBuild();
await this.validate();
console.log('Build pipeline completed successfully!');
} catch (error) {
console.error('Build pipeline failed:', error);
process.exit(1);
}
}
async preBuild() {
console.log('Pre-build tasks...');
// Clean build directory
await this.cleanBuildDir();
// Check dependencies
await this.checkDependencies();
// Run linting
await this.runLinting();
// Run tests
await this.runTests();
}
async build() {
console.log('Building application...');
const buildCommand =
process.env.NODE_ENV === 'production'
? 'npm run build:prod'
: 'npm run build';
execSync(buildCommand, { stdio: 'inherit' });
}
async postBuild() {
console.log('Post-build tasks...');
// Generate build manifest
await this.generateBuildManifest();
// Optimize assets
await this.optimizeAssets();
// Generate service worker
await this.generateServiceWorker();
// Copy additional files
await this.copyStaticFiles();
}
async validate() {
console.log('Validating build...');
// Check bundle sizes
await this.checkBundleSizes();
// Validate HTML
await this.validateHTML();
// Check for missing files
await this.checkRequiredFiles();
}
async cleanBuildDir() {
try {
await fs.rmdir(this.config.buildDir, { recursive: true });
await fs.mkdir(this.config.buildDir, { recursive: true });
console.log('Build directory cleaned');
} catch (error) {
// Directory might not exist, that's okay
}
}
async checkDependencies() {
try {
execSync('npm audit --audit-level moderate', { stdio: 'pipe' });
console.log('Dependencies check passed');
} catch (error) {
console.warn('Dependency vulnerabilities detected');
}
}
async runLinting() {
try {
execSync('npm run lint', { stdio: 'pipe' });
console.log('Linting passed');
} catch (error) {
throw new Error('Linting failed');
}
}
async runTests() {
try {
execSync('npm test -- --coverage --watchAll=false', { stdio: 'pipe' });
console.log('Tests passed');
} catch (error) {
throw new Error('Tests failed');
}
}
async generateBuildManifest() {
const buildInfo = {
buildTime: new Date().toISOString(),
version: process.env.npm_package_version || '1.0.0',
environment: process.env.NODE_ENV || 'development',
commit: this.getGitCommit(),
branch: this.getGitBranch(),
};
await fs.writeFile(
path.join(this.config.buildDir, 'build-manifest.json'),
JSON.stringify(buildInfo, null, 2)
);
console.log('Build manifest generated');
}
async optimizeAssets() {
// Compress images
try {
execSync('npm run optimize:images', { stdio: 'pipe' });
console.log('Images optimized');
} catch (error) {
console.warn('Image optimization failed');
}
}
async generateServiceWorker() {
if (this.config.pwa) {
try {
execSync('npm run build:sw', { stdio: 'pipe' });
console.log('Service worker generated');
} catch (error) {
console.warn('Service worker generation failed');
}
}
}
async copyStaticFiles() {
const staticFiles = ['robots.txt', 'sitemap.xml', '.htaccess'];
for (const file of staticFiles) {
try {
await fs.copyFile(
path.join('public', file),
path.join(this.config.buildDir, file)
);
} catch (error) {
// File might not exist, that's okay
}
}
console.log('Static files copied');
}
async checkBundleSizes() {
const bundleFiles = await fs.readdir(this.config.buildDir);
const jsFiles = bundleFiles.filter((file) => file.endsWith('.js'));
for (const file of jsFiles) {
const filePath = path.join(this.config.buildDir, file);
const stats = await fs.stat(filePath);
const sizeKB = stats.size / 1024;
if (sizeKB > 250) {
console.warn(`Large bundle detected: ${file} (${sizeKB.toFixed(1)}KB)`);
}
}
console.log('Bundle sizes checked');
}
async validateHTML() {
try {
const htmlFiles = await fs.readdir(this.config.buildDir);
const htmlFile = htmlFiles.find((file) => file.endsWith('.html'));
if (htmlFile) {
const content = await fs.readFile(
path.join(this.config.buildDir, htmlFile),
'utf8'
);
// Basic HTML validation
if (!content.includes('<!DOCTYPE html>')) {
throw new Error('Missing DOCTYPE declaration');
}
console.log('HTML validation passed');
}
} catch (error) {
console.warn('HTML validation failed:', error.message);
}
}
async checkRequiredFiles() {
const requiredFiles = ['index.html'];
for (const file of requiredFiles) {
try {
await fs.access(path.join(this.config.buildDir, file));
} catch (error) {
throw new Error(`Required file missing: ${file}`);
}
}
console.log('Required files check passed');
}
getGitCommit() {
try {
return execSync('git rev-parse HEAD', { encoding: 'utf8' }).trim();
} catch {
return 'unknown';
}
}
getGitBranch() {
try {
return execSync('git rev-parse --abbrev-ref HEAD', {
encoding: 'utf8',
}).trim();
} catch {
return 'unknown';
}
}
}
// Usage
const pipeline = new BuildPipeline({
buildDir: 'dist',
pwa: true,
});
pipeline.runPipeline();
Conclusion
Modern JavaScript build tools are essential for creating optimized, production-ready applications. Each tool has its strengths: Webpack for complex configurations and extensive plugin ecosystem, Vite for fast development and modern defaults, Rollup for libraries and tree-shaking, and esbuild for speed. Understanding how to configure and optimize these tools enables you to create efficient build pipelines that improve development experience and application performance. Choose the right tool based on your project requirements, team expertise, and performance needs.