Skip to main content

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"
}
}
Webpack Best Practices
  • 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 vs Webpack

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
})
]
});
Bundle Size Matters

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
Next Steps

In Module 31, you'll get an Overview of Modern Frameworks including React, Vue, and Angular basics.


Practice Exercises

  1. Set up a Webpack project with Babel and CSS loaders
  2. Configure code splitting for a multi-page application
  3. Create a Vite project with HMR and environment variables
  4. Analyze and optimize bundle size
  5. Set up Module Federation between two applications
  6. Create custom Webpack plugin
  7. Configure production build with all optimizations
  8. Compare build times between Webpack, Vite, and Parcel

Additional Resources