Code Splitting Strategies - Complete Guide
Published: September 25, 2024 | Reading time: 21 minutes
Code Splitting Overview
Code splitting improves application performance by loading only necessary code:
Code Splitting Benefits
# Code Splitting Benefits
- Faster initial page load
- Reduced bundle size
- Better caching strategies
- Improved user experience
- Lower bandwidth usage
- Better Core Web Vitals
- Enhanced mobile performance
Dynamic Imports
ES6 Dynamic Imports
Dynamic Import Implementation
# Dynamic Import Strategies
# 1. Basic Dynamic Import
// Load module on demand
async function loadModule() {
try {
const module = await import('./heavy-module.js');
return module.default;
} catch (error) {
console.error('Failed to load module:', error);
}
}
// Usage
const heavyModule = await loadModule();
heavyModule.doSomething();
# 2. Conditional Loading
class ConditionalLoader {
constructor() {
this.loadedModules = new Map();
}
async loadIfNeeded(modulePath, condition) {
if (condition && !this.loadedModules.has(modulePath)) {
try {
const module = await import(modulePath);
this.loadedModules.set(modulePath, module);
return module;
} catch (error) {
console.error(`Failed to load ${modulePath}:`, error);
return null;
}
}
return this.loadedModules.get(modulePath);
}
async loadAdminModule() {
return await this.loadIfNeeded('./admin.js', this.isAdmin());
}
async loadAnalyticsModule() {
return await this.loadIfNeeded('./analytics.js', this.hasAnalyticsConsent());
}
isAdmin() {
return localStorage.getItem('userRole') === 'admin';
}
hasAnalyticsConsent() {
return localStorage.getItem('analyticsConsent') === 'true';
}
}
# 3. Route-Based Code Splitting
// React Router example
import React, { Suspense, lazy } from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
// Lazy load components
const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));
const Contact = lazy(() => import('./pages/Contact'));
const Admin = lazy(() => import('./pages/Admin'));
function App() {
return (
Loading... Something went wrong loading this component.
Webpack Code Splitting
Webpack Configuration
Webpack Code Splitting Setup
# Webpack Code Splitting Configuration
# 1. Basic Webpack Configuration
const path = require('path');
module.exports = {
entry: {
main: './src/index.js',
vendor: './src/vendor.js'
},
output: {
filename: '[name].[contenthash].js',
path: path.resolve(__dirname, 'dist'),
clean: true
},
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all'
}
}
}
}
};
# 2. Advanced Split Chunks Configuration
module.exports = {
optimization: {
splitChunks: {
chunks: 'all',
minSize: 20000,
maxSize: 244000,
cacheGroups: {
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true
},
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
priority: -10,
chunks: 'all'
},
common: {
name: 'common',
minChunks: 2,
priority: -5,
reuseExistingChunk: true
},
styles: {
name: 'styles',
test: /\.(css|scss|sass)$/,
chunks: 'all',
enforce: true
}
}
}
}
};
# 3. Dynamic Import with Webpack
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
plugins: ['@babel/plugin-syntax-dynamic-import']
}
}
}
]
}
};
# 4. Bundle Analysis
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
plugins: [
new BundleAnalyzerPlugin({
analyzerMode: 'static',
openAnalyzer: false,
reportFilename: 'bundle-report.html'
})
]
};
# 5. Custom Split Chunks Strategy
module.exports = {
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
// React and related libraries
react: {
test: /[\\/]node_modules[\\/](react|react-dom|react-router)[\\/]/,
name: 'react',
chunks: 'all',
priority: 20
},
// Utility libraries
utils: {
test: /[\\/]node_modules[\\/](lodash|moment|date-fns)[\\/]/,
name: 'utils',
chunks: 'all',
priority: 15
},
// UI libraries
ui: {
test: /[\\/]node_modules[\\/](@material-ui|antd|bootstrap)[\\/]/,
name: 'ui',
chunks: 'all',
priority: 10
},
// Default vendor chunk
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendor',
chunks: 'all',
priority: 5
}
}
}
}
};
# 6. Webpack Performance Budget
module.exports = {
performance: {
maxAssetSize: 250000,
maxEntrypointSize: 250000,
hints: 'warning',
assetFilter: function(assetFilename) {
return assetFilename.endsWith('.js') || assetFilename.endsWith('.css');
}
}
};
# 7. Webpack Plugin for Code Splitting
class CodeSplittingPlugin {
constructor(options = {}) {
this.options = {
chunkSizeLimit: 244000,
maxChunks: 10,
...options
};
}
apply(compiler) {
compiler.hooks.emit.tapAsync('CodeSplittingPlugin', (compilation, callback) => {
const chunks = compilation.chunks;
const oversizedChunks = chunks.filter(chunk =>
chunk.size() > this.options.chunkSizeLimit
);
if (oversizedChunks.length > 0) {
console.warn('Oversized chunks detected:', oversizedChunks.map(chunk => ({
name: chunk.name,
size: chunk.size()
})));
}
callback();
});
}
}
React Code Splitting
React-Specific Strategies
React Code Splitting Implementation
# React Code Splitting Strategies
# 1. React.lazy() Implementation
import React, { Suspense, lazy } from 'react';
// Lazy load components
const Dashboard = lazy(() => import('./Dashboard'));
const Profile = lazy(() => import('./Profile'));
const Settings = lazy(() => import('./Settings'));
function App() {
const [currentView, setCurrentView] = React.useState('dashboard');
const renderCurrentView = () => {
switch (currentView) {
case 'dashboard':
return ;
case 'profile':
return ;
case 'settings':
return ;
default:
return ;
}
};
return (
Loading... }>
{renderCurrentView()}
Loading...
;
}
if (error) {
return Error loading component: {error.message}
;
}
return Loading chart...
);
# 3. Custom Hook for Code Splitting
function useCodeSplit(importFunc, deps = []) {
const [component, setComponent] = React.useState(null);
const [loading, setLoading] = React.useState(true);
const [error, setError] = React.useState(null);
React.useEffect(() => {
let cancelled = false;
importFunc()
.then(module => {
if (!cancelled) {
setComponent(() => module.default);
setLoading(false);
}
})
.catch(err => {
if (!cancelled) {
setError(err);
setLoading(false);
}
});
return () => {
cancelled = true;
};
}, deps);
return { component: component, loading, error };
}
// Usage
function MyComponent() {
const { component: Chart, loading, error } = useCodeSplit(
() => import('./Chart')
);
if (loading) return Loading chart...
;
if (error) return Error: {error.message}
;
return Loading...
Error loading component: {error}
;
}
if (loading) {
return Loading...
;
}
if (Component) {
return
{Component ? (
) : (
this.props.children
)}
);
}
}
// Usage
Vue.js Code Splitting
Vue-Specific Strategies
Vue.js Code Splitting Implementation
# Vue.js Code Splitting Strategies
# 1. Vue Router Code Splitting
import { createRouter, createWebHistory } from 'vue-router';
const routes = [
{
path: '/',
name: 'Home',
component: () => import('../views/Home.vue')
},
{
path: '/about',
name: 'About',
component: () => import('../views/About.vue')
},
{
path: '/contact',
name: 'Contact',
component: () => import('../views/Contact.vue')
},
{
path: '/admin',
name: 'Admin',
component: () => import('../views/Admin.vue'),
meta: { requiresAuth: true }
}
];
const router = createRouter({
history: createWebHistory(),
routes
});
export default router;
# 2. Vue Component Lazy Loading
// App.vue
Loading...
# 3. Vue Composition API Code Splitting
import { defineAsyncComponent, ref, onMounted } from 'vue';
export default {
setup() {
const showChart = ref(false);
const ChartComponent = ref(null);
const loadChart = async () => {
if (!ChartComponent.value) {
ChartComponent.value = defineAsyncComponent(() =>
import('./Chart.vue')
);
}
showChart.value = true;
};
return {
showChart,
ChartComponent,
loadChart
};
}
};
# 4. Vue Plugin for Code Splitting
// plugins/code-splitting.js
export default {
install(app, options = {}) {
const { preloadOnHover = true } = options;
app.directive('lazy-load', {
mounted(el, binding) {
const importFunc = binding.value;
if (preloadOnHover) {
el.addEventListener('mouseenter', async () => {
if (!el._component) {
try {
const module = await importFunc();
el._component = module.default;
} catch (error) {
console.error('Failed to load component:', error);
}
}
});
}
}
});
}
};
# 5. Vue Mixin for Code Splitting
export const codeSplittingMixin = {
data() {
return {
loadedComponents: new Map()
};
},
methods: {
async loadComponent(importFunc, componentName) {
if (this.loadedComponents.has(componentName)) {
return this.loadedComponents.get(componentName);
}
try {
const module = await importFunc();
this.loadedComponents.set(componentName, module.default);
return module.default;
} catch (error) {
console.error(`Failed to load ${componentName}:`, error);
throw error;
}
}
}
};
# 6. Vue 3 Composition API Hook
import { ref, onMounted } from 'vue';
export function useCodeSplit(importFunc) {
const component = ref(null);
const loading = ref(true);
const error = ref(null);
onMounted(async () => {
try {
const module = await importFunc();
component.value = module.default;
loading.value = false;
} catch (err) {
error.value = err;
loading.value = false;
}
});
return {
component,
loading,
error
};
}
// Usage in component
export default {
setup() {
const { component: Chart, loading, error } = useCodeSplit(
() => import('./Chart.vue')
);
return {
Chart,
loading,
error
};
}
};
Performance Monitoring
Code Splitting Performance Tracking
Performance Monitoring Setup
# Code Splitting Performance Monitoring
# 1. Bundle Size Monitoring
class BundleSizeMonitor {
constructor() {
this.metrics = {
initialLoad: 0,
chunksLoaded: 0,
totalSize: 0,
loadTimes: []
};
}
trackInitialLoad() {
const startTime = performance.now();
window.addEventListener('load', () => {
const loadTime = performance.now() - startTime;
this.metrics.initialLoad = loadTime;
this.recordLoadTime('initial', loadTime);
});
}
trackChunkLoad(chunkName) {
const startTime = performance.now();
return () => {
const loadTime = performance.now() - startTime;
this.metrics.chunksLoaded++;
this.recordLoadTime(chunkName, loadTime);
};
}
recordLoadTime(chunkName, loadTime) {
this.metrics.loadTimes.push({
chunk: chunkName,
time: loadTime,
timestamp: Date.now()
});
}
getAverageLoadTime() {
const totalTime = this.metrics.loadTimes.reduce((sum, entry) => sum + entry.time, 0);
return totalTime / this.metrics.loadTimes.length;
}
getReport() {
return {
...this.metrics,
averageLoadTime: this.getAverageLoadTime(),
chunksPerSecond: this.metrics.chunksLoaded / (this.metrics.initialLoad / 1000)
};
}
}
# 2. Code Splitting Analytics
class CodeSplittingAnalytics {
constructor() {
this.events = [];
this.performanceObserver = new PerformanceObserver(this.handlePerformanceEntry.bind(this));
this.performanceObserver.observe({ entryTypes: ['navigation', 'resource'] });
}
handlePerformanceEntry(list) {
for (const entry of list.getEntries()) {
if (entry.entryType === 'resource' && entry.name.includes('.js')) {
this.trackChunkLoad(entry);
}
}
}
trackChunkLoad(entry) {
const chunkInfo = {
name: this.extractChunkName(entry.name),
size: entry.transferSize,
loadTime: entry.duration,
timestamp: entry.startTime
};
this.events.push(chunkInfo);
// Send to analytics
this.sendToAnalytics('chunk_loaded', chunkInfo);
}
extractChunkName(url) {
const match = url.match(/\/([^\/]+)\.js$/);
return match ? match[1] : 'unknown';
}
sendToAnalytics(eventName, data) {
if (window.gtag) {
gtag('event', eventName, {
chunk_name: data.name,
chunk_size: data.size,
load_time: data.loadTime
});
}
}
trackUserInteraction(interaction) {
this.events.push({
type: 'interaction',
action: interaction,
timestamp: Date.now()
});
}
}
# 3. Webpack Bundle Analyzer Integration
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
plugins: [
new BundleAnalyzerPlugin({
analyzerMode: 'static',
openAnalyzer: false,
reportFilename: 'bundle-report.html',
generateStatsFile: true,
statsFilename: 'bundle-stats.json'
})
]
};
# 4. Real-time Performance Monitoring
class RealTimePerformanceMonitor {
constructor() {
this.metrics = new Map();
this.observers = [];
}
startMonitoring() {
// Monitor chunk loading
this.observeChunkLoading();
// Monitor user interactions
this.observeUserInteractions();
// Monitor memory usage
this.observeMemoryUsage();
}
observeChunkLoading() {
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.entryType === 'resource' && entry.name.includes('.js')) {
this.recordChunkMetric(entry);
}
}
});
observer.observe({ entryTypes: ['resource'] });
this.observers.push(observer);
}
observeUserInteractions() {
['click', 'scroll', 'keydown'].forEach(eventType => {
document.addEventListener(eventType, (event) => {
this.recordInteractionMetric(eventType, event);
});
});
}
observeMemoryUsage() {
if ('memory' in performance) {
setInterval(() => {
this.recordMemoryMetric();
}, 5000);
}
}
recordChunkMetric(entry) {
const metric = {
type: 'chunk',
name: this.extractChunkName(entry.name),
size: entry.transferSize,
loadTime: entry.duration,
timestamp: Date.now()
};
this.metrics.set(`chunk_${metric.name}`, metric);
}
recordInteractionMetric(eventType, event) {
const metric = {
type: 'interaction',
eventType,
timestamp: Date.now(),
target: event.target.tagName
};
this.metrics.set(`interaction_${Date.now()}`, metric);
}
recordMemoryMetric() {
if ('memory' in performance) {
const metric = {
type: 'memory',
used: performance.memory.usedJSHeapSize,
total: performance.memory.totalJSHeapSize,
limit: performance.memory.jsHeapSizeLimit,
timestamp: Date.now()
};
this.metrics.set('memory', metric);
}
}
getMetrics() {
return Array.from(this.metrics.values());
}
cleanup() {
this.observers.forEach(observer => observer.disconnect());
}
}
# 5. Performance Budget Monitoring
class PerformanceBudgetMonitor {
constructor(budget) {
this.budget = {
maxInitialLoad: 3000, // 3 seconds
maxChunkSize: 244000, // 244KB
maxChunks: 10,
...budget
};
this.violations = [];
}
checkBudget(metrics) {
const violations = [];
if (metrics.initialLoad > this.budget.maxInitialLoad) {
violations.push({
type: 'initial_load',
value: metrics.initialLoad,
limit: this.budget.maxInitialLoad,
severity: 'error'
});
}
if (metrics.chunksLoaded > this.budget.maxChunks) {
violations.push({
type: 'chunk_count',
value: metrics.chunksLoaded,
limit: this.budget.maxChunks,
severity: 'warning'
});
}
this.violations.push(...violations);
return violations;
}
getViolations() {
return this.violations;
}
generateReport() {
return {
budget: this.budget,
violations: this.violations,
summary: {
totalViolations: this.violations.length,
errors: this.violations.filter(v => v.severity === 'error').length,
warnings: this.violations.filter(v => v.severity === 'warning').length
}
};
}
}
Best Practices
Code Splitting Guidelines
Code Splitting Best Practices
- Split by route boundaries
- Split vendor libraries
- Use dynamic imports
- Implement error boundaries
- Preload critical chunks
- Monitor bundle sizes
- Use performance budgets
Common Mistakes
- Over-splitting code
- No error handling
- Ignoring loading states
- Not preloading chunks
- Poor chunk naming
- No performance monitoring
- Inconsistent splitting strategy
Summary
Code splitting strategies involve several key components:
- Dynamic Imports: ES6 imports, conditional loading, preloading
- Webpack Configuration: Split chunks, bundle optimization, analysis
- React Strategies: React.lazy, Suspense, error boundaries
- Vue.js Strategies: Route splitting, async components, composition API
- Performance Monitoring: Bundle analysis, real-time tracking, budgets
- Best Practices: Optimization guidelines, common pitfalls
Need More Help?
Struggling with code splitting implementation or need help optimizing your JavaScript bundles? Our performance experts can help you implement effective code splitting strategies.
Get Code Splitting Help