TypeScript Webpack Mastery: Module Federation, Code Splitting & Bundle Optimization Guide
TypeScript Webpack: Module Federation, Code Splitting, and Bundle Optimization
A technical guide to building scalable, optimized TypeScript applications with Webpack 5's advanced module system.
Prerequisites
- Node.js: 14.0.0 or higher
- Webpack: 5.x
- TypeScript: 5.0+ (for
moduleResolution: 'bundler'), 5.3+ (for JSON import assertions)
Getting Started
- Install dependencies:
npm install webpack webpack-cli webpack-dev-server typescript ts-loader html-webpack-plugin css-loader mini-css-extract-plugin terser-webpack-plugin css-minimizer-webpack-plugin webpack-bundle-analyzer --save-dev
-
Create webpack config with Module Federation and optimization settings from below.
-
Configure TypeScript for ESM and bundler module resolution (TypeScript 5.0+ required).
-
Set up bootstrap pattern:
- Create
src/index.tswithimport('./bootstrap') - Create
src/bootstrap.tswith your app initialization
- Create
-
Add build scripts:
{
"scripts": {
"dev": "webpack serve --mode development",
"build": "webpack --mode production",
"analyze": "webpack --profile --json > stats.json && webpack-bundle-analyzer stats.json"
}
}
Base Webpack Configuration
// webpack.config.ts
import type { Configuration } from 'webpack';
import webpack from 'webpack';
import HtmlWebpackPlugin from 'html-webpack-plugin';
import MiniCssExtractPlugin from 'mini-css-extract-plugin';
const config: Configuration = {
entry: './src/index.ts',
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[contenthash].js',
clean: true,
},
resolve: {
extensions: ['.ts', '.tsx', '.js', '.jsx', '.json'],
},
module: {
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/,
},
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, 'css-loader'],
},
],
},
plugins: [
new HtmlWebpackPlugin({
template: './public/index.html',
}),
new MiniCssExtractPlugin({
filename: '[name].[contenthash].css',
}),
],
devtool: 'source-map',
devServer: {
static: './dist',
hot: true,
port: 3000,
historyApiFallback: true,
},
};
export default config;
Module Federation
Module Federation enables runtime sharing of code between independently deployed applications. Each application can expose modules for consumption by others or consume modules from remote applications.
Core Configuration
// webpack.config.ts
import type { Configuration } from 'webpack';
import webpack from 'webpack';
const config: Configuration = {
// ... base config above
plugins: [
new webpack.container.ModuleFederationPlugin({
name: 'hostApp',
filename: 'remoteEntry.js',
exposes: {
'./Button': './src/components/Button',
'./utils': './src/utils/index',
},
remotes: {
remoteApp: 'remoteApp@https://cdn.example.com/remoteEntry.js',
},
shared: {
react: { singleton: true, eager: true, requiredVersion: '^18.0.0' },
'react-dom': { singleton: true, eager: true },
},
}),
],
};
export default config;
Bootstrap Pattern (Required)
Module Federation requires an async bootstrap pattern to prevent "Shared module is not available for eager consumption" errors:
// src/index.ts (entry point)
import('./bootstrap');
// src/bootstrap.ts
import React from 'react';
import { createRoot } from 'react-dom/client';
import App from './App';
const container = document.getElementById('root');
const root = createRoot(container!);
root.render(<App />);
Key Properties
- name: Unique identifier for the container
- filename: Entry file for remote consumers (default:
remoteEntry.js) - exposes: Modules this app shares with others
- remotes: External containers this app consumes
- shared: Dependencies deduplicated at runtime
Shared Dependencies Strategy
// webpack.config.ts
import pkg from './package.json' with { type: 'json' };
// In ModuleFederationPlugin shared config:
shared: {
// Singleton pattern - only one instance allowed
react: {
singleton: true,
eager: true, // Load immediately (required for entry points)
requiredVersion: '^18.0.0',
strictVersion: true, // Throw on version mismatch
},
// Multiple versions allowed with fallback
lodash: {
eager: false,
requiredVersion: pkg.dependencies.lodash,
},
}
Eager vs Lazy Loading for Shared Dependencies
-
Eager (
eager: true): Required when the shared module is used synchronously in your entry point or during application bootstrap. Webpack downloads eager dependencies with the initial bundle. Use for critical libraries like React that must be available immediately. -
Lazy (
eager: false): Default behavior. Dependencies load on-demand when first accessed. Reduces initial bundle size but adds async overhead. Use for non-critical shared modules that aren't needed at startup.
shared: {
react: { singleton: true, eager: true }, // Entry point uses React
'charting-lib': { eager: false }, // Loaded when first imported
}
Version Compatibility Between Host and Remote
When host and remote applications share dependencies, version mismatches can cause runtime errors:
shared: {
react: {
singleton: true,
requiredVersion: '^18.0.0',
strictVersion: true, // Throws error on mismatch
// strictVersion: false, // Warns but allows different versions
},
}
Best practices:
- Use
singleton: truefor libraries with internal state (React, Redux, context providers) - Set
strictVersion: truein development to catch mismatches early - Align major versions across host and remote applications
- Use semantic versioning ranges (
^18.0.0allows minor/patch updates) - Test remotes against multiple host versions during development
Error Boundary Component
// src/components/ErrorBoundary.tsx
import React, { Component, ErrorInfo, ReactNode } from 'react';
interface Props {
children: ReactNode;
fallback?: ReactNode;
}
interface State {
hasError: boolean;
}
class ErrorBoundary extends Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(): State {
return { hasError: true };
}
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
console.error('ErrorBoundary caught:', error, errorInfo);
}
render() {
if (this.state.hasError) {
return this.props.fallback || <div>Something went wrong.</div>;
}
return this.props.children;
}
}
export default ErrorBoundary;
TypeScript Types for Federated Modules
// src/types/federated.d.ts
declare module 'remoteApp/Button' {
import { ComponentType } from 'react';
const Button: ComponentType<{ label: string }>;
export default Button;
}
// src/App.tsx - Using federated modules
import React from 'react';
import ErrorBoundary from './components/ErrorBoundary';
const RemoteButton = React.lazy(() => import('remoteApp/Button'));
function FederatedButton() {
return (
<ErrorBoundary fallback={<div>Failed to load remote button</div>}>
<React.Suspense fallback={<div>Loading...</div>}>
<RemoteButton />
</React.Suspense>
</ErrorBoundary>
);
}
Code Splitting
Code splitting divides your bundle into smaller chunks loaded on demand, reducing initial payload and improving time-to-interactive.
Dynamic Imports
React.lazy requires a module that exports a component as default. If your component uses a named export, re-export it as default:
// HeavyComponent.ts - Named export
export function HeavyComponent() {
return <div>Heavy content</div>;
}
// Option 1: Add default export to the module
export { HeavyComponent as default } from './HeavyComponent';
// Option 2: React.lazy with inline default export
const HeavyComponent = React.lazy(() =>
import('./HeavyComponent').then((module) => ({
default: module.HeavyComponent,
}))
);
// Usage with Suspense
function App() {
return (
<React.Suspense fallback={<div>Loading...</div>}>
<HeavyComponent />
</React.Suspense>
);
}
Webpack SplitChunks Configuration
// webpack.config.ts
export default {
optimization: {
splitChunks: {
chunks: 'all',
minSize: 20000,
minRemainingSize: 0,
minChunks: 1,
maxAsyncRequests: 30,
maxInitialRequests: 30,
enforceSizeThreshold: 50000,
cacheGroups: {
// Vendor chunk for node_modules
vendors: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all',
priority: -10,
},
// Common code shared between chunks
common: {
name: 'common',
minChunks: 2,
priority: -20,
reuseExistingChunk: true,
},
// Separate React ecosystem
react: {
test: /[\\/]node_modules[\\/](react|react-dom|scheduler)[\\/]/,
name: 'react-vendors',
chunks: 'all',
priority: 20,
},
},
},
},
};
Magic Comments for Chunk Control
Magic comments with await must be used inside async functions:
// Named chunk
async function loadModule() {
const Module = await import(/* webpackChunkName: "my-module" */ './Module');
return Module;
}
// Prefetch (idle time, parent chunk done)
async function loadDashboard() {
const Dashboard = await import(/* webpackPrefetch: true */ './Dashboard');
return Dashboard;
}
// Preload (parallel to parent)
async function loadCritical() {
const Critical = await import(/* webpackPreload: true */ './Critical');
return Critical;
}
// Combine multiple flags
async function loadAdmin() {
const Admin = await import(
/* webpackChunkName: "admin" */
/* webpackMode: "lazy" */
/* webpackPrefetch: true */
'./Admin'
);
return Admin;
}
TypeScript Configuration
// tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "react-jsx"
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}
Bundle Optimization
Tree Shaking Requirements
Tree shaking requires ESM syntax. Ensure:
// package.json
{
"type": "module",
"sideEffects": false
}
// Or specify files with side effects
// package.json
{
"sideEffects": ["*.css", "*.global.js"]
}
Minification with Terser
// webpack.config.ts
import TerserPlugin from 'terser-webpack-plugin';
export default {
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
parallel: true,
terserOptions: {
compress: {
drop_console: true,
drop_debugger: true,
pure_funcs: ['console.log'],
},
mangle: {
safari10: true,
},
format: {
comments: false,
},
},
extractComments: false,
}),
],
},
};
CSS Optimization
// webpack.config.ts
import CssMinimizerPlugin from 'css-minimizer-webpack-plugin';
export default {
optimization: {
minimizer: [
'...', // Keep existing minimizers (extends default minimizers)
new CssMinimizerPlugin({
minimizerOptions: {
preset: ['advanced', { discardComments: { removeAll: true } }],
},
}),
],
},
};
Production Optimizations
// webpack.config.ts - production mode
export default {
mode: 'production',
optimization: {
usedExports: true,
sideEffects: true,
providedExports: true,
concatenateModules: true,
runtimeChunk: 'single',
moduleIds: 'deterministic',
chunkIds: 'deterministic',
},
performance: {
hints: 'warning',
maxEntrypointSize: 512000,
maxAssetSize: 512000,
},
};
Bundle Analysis
// webpack.config.ts
import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
export default {
plugins: [
new BundleAnalyzerPlugin({
analyzerMode: 'static',
openAnalyzer: false,
generateStatsFile: true,
}),
],
};
Share this Guide:
More Guides
eBPF Networking: High-Performance Policy Enforcement, Traffic Mirroring, and Load Balancing
Master kernel-level networking with eBPF: implement XDP firewalls, traffic mirroring for observability, and Maglev load balancing with Direct Server Return for production-grade infrastructure.
18 min readFinOps Reporting Mastery: Cost Attribution, Trend Analysis & Executive Dashboards
Technical blueprint for building automated cost visibility pipelines with SQL-based attribution, Python anomaly detection, and executive decision dashboards.
4 min readJava Performance Mastery: Complete JVM Tuning Guide for Production Systems
Master Java performance optimization with comprehensive JVM tuning, garbage collection algorithms, and memory management strategies for production microservices and distributed systems.
14 min readPrisma vs TypeORM vs Drizzle: Performance Benchmarks for Node.js Applications
A technical deep-dive comparing three leading TypeScript ORMs on bundle size, cold start overhead, and runtime performance to help you choose the right tool for serverless and traditional Node.js deployments.
8 min readPlatform Engineering Roadmap: From Ad-Hoc Tooling to Mature Internal Developer Platforms
A practical guide to advancing platform maturity using the CNCF framework, capability assessment matrices, and phased strategy for building self-service developer platforms.
9 min readContinue Reading
eBPF Networking: High-Performance Policy Enforcement, Traffic Mirroring, and Load Balancing
Master kernel-level networking with eBPF: implement XDP firewalls, traffic mirroring for observability, and Maglev load balancing with Direct Server Return for production-grade infrastructure.
18 min readFinOps Reporting Mastery: Cost Attribution, Trend Analysis & Executive Dashboards
Technical blueprint for building automated cost visibility pipelines with SQL-based attribution, Python anomaly detection, and executive decision dashboards.
4 min readJava Performance Mastery: Complete JVM Tuning Guide for Production Systems
Master Java performance optimization with comprehensive JVM tuning, garbage collection algorithms, and memory management strategies for production microservices and distributed systems.
14 min readReady to Supercharge Your Development Workflow?
Join thousands of engineering teams using MatterAI to accelerate code reviews, catch bugs earlier, and ship faster.
