Module 30: Build Tools and Module Bundlers
Build tools and module bundlers transform and optimize your code for production. They handle transpilation, bundling, minification, and asset management.
1. Why Build Tools?
1.1 Problems They Solve
// Problem 1: Module System Compatibility
// Not all browsers support ES modules
import { add } from './math.js'; // Might not work in older browsers
// Problem 2: Performance
// Loading many files individually is slow
<script src="module1.js"></script>
<script src="module2.js"></script>
<script src="module3.js"></script>
<!-- ... 100 more files -->
// Problem 3: Modern JavaScript Features
// Older browsers don't support ES6+
const greet = (name) => `Hello, ${name}`;
const user = { name: 'John', ...defaults };
// Problem 4: Asset Management
// CSS, images, fonts need to be processed
import './styles.css';
import logo from './logo.png';
// Problem 5: Development Experience
// Hot reloading, source maps, error messages
1.2 What Build Tools Do
// Input: Modern JavaScript with imports
// src/index.js
import { formatDate } from './utils/date.js';
import { fetchUser } from './api/user.js';
import './styles.css';
async function init() {
const user = await fetchUser(1);
const date = formatDate(user.joinDate);
document.getElementById('app').innerHTML = `
<h1>Welcome, ${user.name}</h1>
<p>Member since ${date}</p>
`;
}
init();
// Output: Bundled, transpiled, minified code
// dist/bundle.js
(function(){var e={./src/utils/date.js:function(e,t,r){"use strict";...}}
// All code combined, transpiled to ES5, minified
2. Webpack
2.1 Basic Setup
// package.json
{
"name": "my-app",
"scripts": {
"build": "webpack",
"dev": "webpack serve"
},
"devDependencies": {
"webpack": "^5.0.0",
"webpack-cli": "^5.0.0",
"webpack-dev-server": "^4.0.0"
}
}
// webpack.config.js
const path = require('path');
module.exports = {
// Entry point
entry: './src/index.js',
// Output configuration
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js',
clean: true // Clean dist folder before build
},
// Development server
devServer: {
static: './dist',
port: 3000,
hot: true // Hot Module Replacement
},
// Source maps for debugging
devtool: 'source-map',
// Mode: 'development' or 'production'
mode: 'development'
};
2.2 Loaders
Loaders transform files before bundling.
// webpack.config.js
module.exports = {
module: {
rules: [
// Babel loader: Transpile JavaScript
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
},
// CSS loader
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
},
// SASS/SCSS loader
{
test: /\.scss$/,
use: ['style-loader', 'css-loader', 'sass-loader']
},
// Image loader
{
test: /\.(png|jpg|gif|svg)$/,
type: 'asset/resource'
},
// Font loader
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
type: 'asset/resource'
}
]
}
};
// Usage in code
import './styles.css';
import logo from './logo.png';
const img = document.createElement('img');
img.src = logo;
document.body.appendChild(img);
2.3 Plugins
Plugins perform additional tasks.
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
plugins: [
// Generate HTML file
new HtmlWebpackPlugin({
template: './src/index.html',
minify: true
}),
// Extract CSS to separate file
new MiniCssExtractPlugin({
filename: '[name].[contenthash].css'
}),
// Clean dist folder
new CleanWebpackPlugin()
]
};
2.4 Code Splitting
Split code into multiple bundles for better performance.
// webpack.config.js
module.exports = {
entry: {
main: './src/index.js',
admin: './src/admin.js'
},
output: {
filename: '[name].[contenthash].js',
path: path.resolve(__dirname, 'dist')
},
optimization: {
// Split vendor code
splitChunks: {
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all'
}
}
}
}
};
// Dynamic imports (code splitting at runtime)
// src/index.js
async function loadModule() {
const module = await import('./heavy-module.js');
module.doSomething();
}
// Lazy loading routes
async function navigateTo(route) {
if (route === '/dashboard') {
const { Dashboard } = await import('./pages/Dashboard.js');
new Dashboard().render();
} else if (route === '/profile') {
const { Profile } = await import('./pages/Profile.js');
new Profile().render();
}
}
2.5 Production Configuration
// webpack.prod.js
const { merge } = require('webpack-merge');
const common = require('./webpack.common.js');
const TerserPlugin = require('terser-webpack-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
module.exports = merge(common, {
mode: 'production',
devtool: 'source-map',
optimization: {
minimize: true,
minimizer: [
// Minify JavaScript
new TerserPlugin({
terserOptions: {
compress: {
drop_console: true // Remove console.log
}
}
}),
// Minify CSS
new CssMinimizerPlugin()
],
// Split chunks
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
priority: 10
},
common: {
minChunks: 2,
priority: 5,
reuseExistingChunk: true
}
}
}
},
performance: {
hints: 'warning',
maxEntrypointSize: 512000,
maxAssetSize: 512000
}
});
// package.json
{
"scripts": {
"build": "webpack --config webpack.prod.js",
"dev": "webpack serve --config webpack.dev.js"
}
}
- Use code splitting for large applications
- Extract CSS in production
- Enable tree shaking (automatic in production mode)
- Use caching with contenthash
- Minimize bundle size
3. Vite
Modern, fast build tool leveraging native ES modules.
3.1 Why Vite?
// Traditional bundler (Webpack)
// 1. Bundle entire app
// 2. Start dev server
// 3. Rebuild on changes
// Time: Seconds to minutes for large apps
// Vite
// 1. Start dev server instantly
// 2. Bundle on-demand (only requested modules)
// 3. HMR (Hot Module Replacement) is instant
// Time: Milliseconds
3.2 Basic Setup
// Create Vite project
// npm create vite@latest my-app
// cd my-app
// npm install
// npm run dev
// package.json
{
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"devDependencies": {
"vite": "^5.0.0"
}
}
// vite.config.js
import { defineConfig } from 'vite';
export default defineConfig({
// Server configuration
server: {
port: 3000,
open: true, // Open browser on start
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true
}
}
},
// Build configuration
build: {
outDir: 'dist',
sourcemap: true,
minify: 'terser',
rollupOptions: {
output: {
manualChunks: {
vendor: ['lodash', 'axios']
}
}
}
},
// Resolve aliases
resolve: {
alias: {
'@': '/src',
'@components': '/src/components'
}
}
});
3.3 Vite Features
// Hot Module Replacement (HMR)
// src/counter.js
export let count = 0;
export function increment() {
count++;
render();
}
function render() {
document.getElementById('count').textContent = count;
}
// HMR API
if (import.meta.hot) {
import.meta.hot.accept((newModule) => {
if (newModule) {
count = newModule.count;
render();
}
});
}
// Environment variables
// .env
VITE_API_URL=https://api.example.com
VITE_APP_TITLE=My App
// src/config.js
export const config = {
apiUrl: import.meta.env.VITE_API_URL,
title: import.meta.env.VITE_APP_TITLE,
mode: import.meta.env.MODE // 'development' or 'production'
};
// Asset imports
// src/index.js
import logo from './assets/logo.png'; // Returns URL
import styles from './styles.css?inline'; // Inline CSS
import worker from './worker?worker'; // Web Worker
// JSON import
import data from './data.json';
console.log(data);
// Dynamic imports
const module = await import('./module.js');
// Glob imports
const modules = import.meta.glob('./modules/*.js');
// Returns: { './modules/a.js': () => import('./modules/a.js'), ... }
const modulesEager = import.meta.glob('./modules/*.js', { eager: true });
// Returns: { './modules/a.js': { default: ... }, ... }
3.4 Vite Plugins
// vite.config.js
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import vue from '@vitejs/plugin-vue';
export default defineConfig({
plugins: [
react(), // React support
// or
vue(), // Vue support
// Custom plugin
{
name: 'custom-plugin',
transform(code, id) {
if (id.endsWith('.custom')) {
return {
code: transformCustomFile(code),
map: null
};
}
},
handleHotUpdate({ file, server }) {
if (file.endsWith('.custom')) {
console.log('Custom file updated:', file);
server.ws.send({
type: 'custom',
event: 'file-updated'
});
}
}
}
]
});
Vite: Faster dev server, simpler config, modern by default Webpack: More mature, extensive plugin ecosystem, more configuration options
4. Parcel
Zero-configuration bundler.
4.1 Parcel Setup
// Install
// npm install --save-dev parcel
// package.json
{
"scripts": {
"start": "parcel src/index.html",
"build": "parcel build src/index.html"
}
}
// src/index.html
<!DOCTYPE html>
<html>
<head>
<title>My App</title>
<link rel="stylesheet" href="./styles.css">
</head>
<body>
<div id="app"></div>
<script type="module" src="./index.js"></script>
</body>
</html>
// src/index.js
import { render } from './app';
import './styles.css';
render(document.getElementById('app'));
// That's it! No configuration needed
4.2 Parcel Features
// Automatic transforms
// TypeScript
import { User } from './types.ts'; // Automatically transpiled
// JSX (React)
import React from 'react';
const App = () => <div>Hello</div>; // Automatically transformed
// Asset imports
import logo from './logo.png';
import data from './data.json';
import styles from './styles.css';
// Dynamic imports
const module = await import('./module.js');
// Environment variables
// .env
API_URL=https://api.example.com
// src/config.js
export const apiUrl = process.env.API_URL;
// Production build optimizations (automatic)
// - Minification
// - Tree shaking
// - Code splitting
// - Image optimization
// - CSS optimization
5. Build Optimization Techniques
5.1 Tree Shaking
Remove unused code.
// utils.js (ES modules)
export function add(a, b) {
return a + b;
}
export function subtract(a, b) {
return a - b;
}
export function multiply(a, b) {
return a * b;
}
export function divide(a, b) {
return a / b;
}
// main.js
import { add, multiply } from './utils.js';
console.log(add(2, 3));
console.log(multiply(2, 3));
// Production build only includes add() and multiply()
// subtract() and divide() are tree-shaken (removed)
// ❌ Tree shaking won't work with:
// CommonJS (require/module.exports)
const utils = require('./utils'); // Entire module included
// Dynamic imports with variables
const funcName = 'add';
import(`./${funcName}.js`); // Can't analyze at build time
// Side effects
import './analytics.js'; // Must be included (side effects)
5.2 Code Splitting Strategies
// Route-based splitting
const routes = {
'/': () => import('./pages/Home.js'),
'/about': () => import('./pages/About.js'),
'/dashboard': () => import('./pages/Dashboard.js')
};
async function navigate(path) {
const loader = routes[path];
if (loader) {
const page = await loader();
page.default.render();
}
}
// Component-based splitting
// Modal is heavy, load only when needed
async function showModal() {
const { Modal } = await import('./components/Modal.js');
new Modal().show();
}
// Library splitting
// Heavy libraries loaded separately
async function loadChart() {
const Chart = await import('chart.js');
return new Chart.default(ctx, config);
}
// Prefetching
<link rel="prefetch" href="/dashboard.chunk.js"> // Load during idle time
<link rel="preload" href="/critical.chunk.js"> // Load immediately
// Webpack magic comments
const Dashboard = import(
/* webpackChunkName: "dashboard" */
/* webpackPrefetch: true */
'./pages/Dashboard.js'
);
5.3 Bundle Analysis
// Webpack Bundle Analyzer
// npm install --save-dev webpack-bundle-analyzer
// webpack.config.js
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
plugins: [
new BundleAnalyzerPlugin({
analyzerMode: 'static', // Generate HTML report
openAnalyzer: true
})
]
};
// Analyze bundle
// npm run build
// Opens visualization showing:
// - Bundle size
// - Module composition
// - Duplicate dependencies
// - Large libraries
// Vite bundle analysis
// npm install --save-dev rollup-plugin-visualizer
// vite.config.js
import { visualizer } from 'rollup-plugin-visualizer';
export default defineConfig({
plugins: [
visualizer({
open: true,
gzipSize: true,
brotliSize: true
})
]
});
Every 100KB of JavaScript takes ~1 second to parse and compile on mobile devices. Keep bundles small!
6. Module Federation
Share code between applications at runtime.
// Webpack Module Federation
// host-app/webpack.config.js
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'host',
remotes: {
app1: 'app1@http://localhost:3001/remoteEntry.js',
app2: 'app2@http://localhost:3002/remoteEntry.js'
},
shared: {
react: { singleton: true },
'react-dom': { singleton: true }
}
})
]
};
// remote-app/webpack.config.js
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'app1',
filename: 'remoteEntry.js',
exposes: {
'./Button': './src/components/Button',
'./Header': './src/components/Header'
},
shared: {
react: { singleton: true },
'react-dom': { singleton: true }
}
})
]
};
// host-app/src/index.js
// Use remote components
const Button = React.lazy(() => import('app1/Button'));
const Header = React.lazy(() => import('app2/Header'));
function App() {
return (
<div>
<React.Suspense fallback="Loading...">
<Header />
<Button />
</React.Suspense>
</div>
);
}
7. Real-World Build Configuration
7.1 Complete Webpack Setup
// webpack.common.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
entry: {
main: './src/index.js'
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[contenthash].js',
publicPath: '/'
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: 'babel-loader'
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader', 'postcss-loader']
},
{
test: /\.(png|jpg|gif|svg)$/,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 8 * 1024 // 8kb
}
}
}
]
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
template: './src/index.html',
minify: {
removeComments: true,
collapseWhitespace: true
}
})
],
resolve: {
alias: {
'@': path.resolve(__dirname, 'src'),
'@components': path.resolve(__dirname, 'src/components')
},
extensions: ['.js', '.jsx', '.json']
}
};
// webpack.dev.js
const { merge } = require('webpack-merge');
const common = require('./webpack.common.js');
module.exports = merge(common, {
mode: 'development',
devtool: 'eval-source-map',
devServer: {
static: './dist',
port: 3000,
hot: true,
historyApiFallback: true,
compress: true
}
});
// webpack.prod.js
const { merge } = require('webpack-merge');
const common = require('./webpack.common.js');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');
module.exports = merge(common, {
mode: 'production',
devtool: 'source-map',
module: {
rules: [
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader']
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: '[name].[contenthash].css'
})
],
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
terserOptions: {
compress: { drop_console: true }
}
}),
new CssMinimizerPlugin()
],
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
priority: 10
}
}
},
runtimeChunk: 'single'
}
});
Summary
In this module, you learned:
- ✅ Why build tools are essential
- ✅ Webpack configuration and concepts
- ✅ Loaders and plugins
- ✅ Code splitting strategies
- ✅ Vite: modern, fast build tool
- ✅ Parcel: zero-config bundler
- ✅ Tree shaking and optimization
- ✅ Bundle analysis
- ✅ Module Federation
- ✅ Production-ready configurations
In Module 31, you'll get an Overview of Modern Frameworks including React, Vue, and Angular basics.
Practice Exercises
- Set up a Webpack project with Babel and CSS loaders
- Configure code splitting for a multi-page application
- Create a Vite project with HMR and environment variables
- Analyze and optimize bundle size
- Set up Module Federation between two applications
- Create custom Webpack plugin
- Configure production build with all optimizations
- Compare build times between Webpack, Vite, and Parcel