On-demand loading and lazy loading of routes
Concepts of On-Demand Loading and Route Lazy Loading
On-demand loading is an optimization technique that allows an application to load specific resources only when needed, rather than loading all content at once. Route lazy loading is a specific implementation of on-demand loading at the routing level. It splits components corresponding to different routes into separate code chunks and loads the related resources only when the corresponding route is accessed. This technology can significantly reduce initial loading time and improve user experience.
Code Splitting in Webpack
Webpack has built-in support for code splitting since version 2, primarily implemented in three ways:
- Entry Points: Manually split code using the
entry
configuration. - Preventing Duplication: Use
SplitChunksPlugin
to deduplicate and separate chunks. - Dynamic Imports: Split code through inline function calls within modules.
Dynamic imports are the most commonly used method for implementing on-demand loading. Webpack provides two syntaxes:
// Using the ES proposal's import() syntax
import(/* webpackChunkName: "moduleA" */ './moduleA').then(module => {
// Use the module
});
// Using Webpack-specific require.ensure
require.ensure([], function(require) {
const moduleB = require('./moduleB');
// Use the module
}, 'moduleB');
Implementation Methods for Route Lazy Loading
In modern frontend frameworks, route lazy loading is typically combined with dynamic imports. Here are implementation examples for various frameworks:
Implementation in React
import React, { lazy, Suspense } from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
const Home = lazy(() => import('./components/Home'));
const About = lazy(() => import('./components/About'));
function App() {
return (
<Router>
<Suspense fallback={<div>Loading...</div>}>
<Switch>
<Route exact path="/" component={Home} />
<Route path="/about" component={About} />
</Switch>
</Suspense>
</Router>
);
}
Implementation in Vue
const router = new VueRouter({
routes: [
{
path: '/',
component: () => import('./components/Home.vue')
},
{
path: '/about',
component: () => import('./components/About.vue')
}
]
});
Implementation in Angular
const routes: Routes = [
{
path: '',
loadChildren: () => import('./home/home.module').then(m => m.HomeModule)
},
{
path: 'about',
loadChildren: () => import('./about/about.module').then(m => m.AboutModule)
}
];
Webpack Configuration Optimization
To achieve efficient on-demand loading, appropriate Webpack configuration is required:
module.exports = {
output: {
filename: '[name].[contenthash].js',
chunkFilename: '[name].[contenthash].chunk.js',
path: path.resolve(__dirname, 'dist'),
publicPath: '/'
},
optimization: {
splitChunks: {
chunks: 'all',
minSize: 30000,
maxSize: 0,
minChunks: 1,
maxAsyncRequests: 5,
maxInitialRequests: 3,
automaticNameDelimiter: '~',
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true
}
}
}
}
};
Key configuration items explained:
chunkFilename
: Defines the name of non-entry chunks.splitChunks
: Controls how code is split.publicPath
: Specifies the public path for on-demand loaded files.
Preloading and Prefetching
Webpack 4.6.0+ supports resource preloading through magic comments:
import(
/* webpackPrefetch: true */
/* webpackChunkName: "chart" */
'./charting-library'
).then(({ initChart }) => {
initChart();
});
Two types of resource hints:
prefetch
: Loads during idle time, potentially for future navigation.preload
: Loads with medium priority, possibly needed in the current navigation.
Performance Optimization Practices
1. Third-Party Library Separation
Separate third-party libraries into their own bundle:
optimization: {
splitChunks: {
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,
name: 'vendor',
chunks: 'all',
},
},
},
}
2. Grouping by Route
Bundle components under the same route together:
const About = lazy(() => import(/* webpackChunkName: "about" */ './About'));
const Contact = lazy(() => import(/* webpackChunkName: "contact" */ './Contact'));
3. Critical CSS Extraction
Extract critical CSS using mini-css-extract-plugin
:
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader'
]
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: '[name].css',
chunkFilename: '[id].css'
})
]
};
Common Issues and Solutions
1. Loading State Management
Use Suspense and error boundaries to handle loading states:
class ErrorBoundary extends React.Component {
state = { hasError: false };
static getDerivedStateFromError(error) {
return { hasError: true };
}
render() {
if (this.state.hasError) {
return <h1>Error loading component</h1>;
}
return this.props.children;
}
}
function App() {
return (
<ErrorBoundary>
<Suspense fallback={<div>Loading...</div>}>
<LazyComponent />
</Suspense>
</ErrorBoundary>
);
}
2. Naming Chunks
Name chunks using magic comments:
const Home = lazy(() => import(/* webpackChunkName: "home" */ './Home'));
3. Duplicate Dependency Issues
Configure splitChunks
to avoid duplicate dependencies:
optimization: {
splitChunks: {
chunks: 'all',
minSize: 0,
cacheGroups: {
commons: {
name: 'commons',
chunks: 'initial',
minChunks: 2
}
}
}
}
Advanced Application Scenarios
1. Dynamic Loading Based on User Behavior
Predictively load resources based on user actions:
const loginButton = document.getElementById('login');
loginButton.addEventListener('mouseover', () => {
import('./LoginModal').then(module => {
// Preload the login module
});
});
2. On-Demand Loading in Server-Side Rendering
Dynamic imports in Next.js:
import dynamic from 'next/dynamic';
const DynamicComponent = dynamic(() => import('../components/hello'), {
loading: () => <p>Loading...</p>,
ssr: false
});
function Home() {
return <DynamicComponent />;
}
3. Lazy Loading Web Workers
Dynamically create Web Workers:
const worker = new Worker(
URL.createObjectURL(
new Blob([
`importScripts('${process.env.PUBLIC_URL}/worker.js');`
])
)
);
Performance Monitoring and Tuning
Use webpack-bundle-analyzer
to analyze bundle size:
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
plugins: [
new BundleAnalyzerPlugin()
]
};
Key performance metrics:
- First Meaningful Paint (FMP)
- Time to Interactive (TTI)
- Total downloaded resource size
- Proportion of on-demand loaded resources
Browser Caching Strategies
Optimize loading performance with long-term caching:
output: {
filename: '[name].[contenthash].js',
chunkFilename: '[name].[contenthash].chunk.js',
},
optimization: {
runtimeChunk: 'single',
moduleIds: 'deterministic',
splitChunks: {
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all',
},
},
},
},
Modern Improvement Solutions
1. Using ES Modules
Native ES modules support dynamic imports:
<script type="module">
import('./module.js').then(module => {
module.default();
});
</script>
2. HTTP/2 Push
Configure server push for critical resources:
location = /index.html {
http2_push /static/js/main.chunk.js;
http2_push /static/css/main.css;
}
3. Using Intersection Observer API
Trigger loading based on viewport:
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
import('./component.js').then(module => {
module.init();
});
observer.unobserve(entry.target);
}
});
});
observer.observe(document.querySelector('.lazy-component'));
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:代码压缩策略与工具选择
下一篇:长缓存优化方案