Code splitting strategy
Code splitting is a technique that breaks down an application into smaller chunks, reducing initial load time by loading resources on demand and improving user experience. Proper use of code splitting can significantly decrease the size of first-screen resources, making it particularly suitable for large single-page applications or complex feature modules.
Dynamic Imports for Basic Splitting
The ES6 dynamic import syntax is the most straightforward way to implement code splitting. By leveraging the Promise returned by the import()
function, modules can be loaded asynchronously:
// Standard import (synchronous)
// import HeavyComponent from './HeavyComponent'
// Dynamic import (asynchronous)
button.addEventListener('click', () => {
import('./HeavyComponent')
.then(module => {
const Component = module.default
// Render the component
})
.catch(err => {
console.error('Module loading failed:', err)
})
})
Webpack automatically splits ./HeavyComponent
and its dependencies into separate chunks, typically outputting filenames like 1.bundle.js
. Dynamic imports are ideal for:
- Route-level component splitting
- Non-critical content like modals
- Complex third-party libraries (e.g., PDF generation tools)
Route-Level Splitting Strategy
A typical implementation for route-level splitting in React Router:
import { lazy, Suspense } from 'react'
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'
const Home = lazy(() => import('./routes/Home'))
const Dashboard = lazy(() => import('./routes/Dashboard'))
function App() {
return (
<Router>
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/dashboard" element={<Dashboard />} />
</Routes>
</Suspense>
</Router>
)
}
In Vue, a similar effect can be achieved with defineAsyncComponent
:
const AsyncComp = defineAsyncComponent(() =>
import('./components/AsyncComponent.vue')
)
Fine-Grained Component Splitting
For complex component trees, more granular splitting can be applied. For example, an e-commerce site's image gallery:
const ProductGallery = React.lazy(() => import('./ProductGallery'))
const ZoomModal = React.lazy(() => import('./ZoomModal'))
function ProductPage() {
const [showZoom, setShowZoom] = useState(false)
return (
<div>
<Suspense fallback={<Spinner />}>
<ProductGallery onZoom={() => setShowZoom(true)} />
</Suspense>
{showZoom && (
<Suspense fallback={null}>
<ZoomModal />
</Suspense>
)}
</div>
)
}
Preloading Optimization Strategies
Common patterns for preloading to enhance user interaction:
// Preload on hover
linkElement.addEventListener('mouseover', () => {
import('./Tooltip').then(preloadModule => {
// Module is preloaded but not executed
})
})
// Preload during idle time
if ('requestIdleCallback' in window) {
requestIdleCallback(() => {
import('./AnalyticsChart')
})
}
Webpack's magic comments allow finer control:
import(
/* webpackPrefetch: true */
/* webpackChunkName: "chart" */
'./ChartComponent'
)
Third-Party Library Splitting
Typical splitting strategies for large third-party libraries:
// Separate moment.js locale files
import moment from 'moment'
import('moment/locale/zh-cn').then(() => moment.locale('zh-cn'))
// On-demand loading for Ant Design components
const { Button } = await import('antd')
Webpack configuration example:
optimization: {
splitChunks: {
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all'
},
antd: {
test: /[\\/]node_modules[\\/]antd[\\/]/,
name: 'antd',
priority: 20
}
}
}
}
Splitting in Server-Side Rendering
Next.js automatic code splitting example:
// pages/index.js
import dynamic from 'next/dynamic'
const DynamicHeader = dynamic(
() => import('../components/header'),
{
loading: () => <p>Loading Header...</p>,
ssr: false // Disable server-side rendering
}
)
export default function Home() {
return <DynamicHeader />
}
Nuxt.js component-level async loading:
<template>
<div>
<LazyTheFooter />
<button @click="showModal = true">Open</button>
<LazyTheModal v-if="showModal" />
</div>
</template>
<script>
export default {
data() {
return { showModal: false }
}
}
</script>
Offloading Tasks to Web Workers
Delegating CPU-intensive tasks to Web Workers:
// main.js
const worker = new Worker('./worker.js')
worker.postMessage({ type: 'CALCULATE', data: largeDataSet })
worker.onmessage = (e) => {
console.log('Result:', e.data)
}
// worker.js
self.onmessage = function(e) {
if (e.data.type === 'CALCULATE') {
const result = heavyComputation(e.data.data)
self.postMessage(result)
}
}
Resource Loading Priority Control
Using rel="preload"
to prioritize critical resources:
<link
rel="preload"
href="critical-chunk.js"
as="script"
crossorigin="anonymous"
>
HTTP/2 Server Push implementation in Node.js:
const http2 = require('http2')
const server = http2.createSecureServer({...})
server.on('stream', (stream, headers) => {
if (headers[':path'] === '/') {
stream.pushStream({ ':path': '/critical.css' }, (err, pushStream) => {
pushStream.respondWithFile('/assets/critical.css')
})
stream.respondWithFile('/index.html')
}
})
Chunk Caching Strategy Optimization
Long-term caching with filename hashing:
output: {
filename: '[name].[contenthash:8].js',
chunkFilename: '[name].[contenthash:8].chunk.js',
}
Runtime chunk separation configuration:
runtimeChunk: {
name: entrypoint => `runtime-${entrypoint.name}`
}
Analysis Tools and Monitoring
Generating visual reports with webpack-bundle-analyzer:
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
module.exports = {
plugins: [
new BundleAnalyzerPlugin({
analyzerMode: 'static',
reportFilename: 'bundle-report.html'
})
]
}
Performance monitoring code example:
const perfObserver = new PerformanceObserver(list => {
list.getEntries().forEach(entry => {
if (entry.name.includes('chunk')) {
console.log(`Chunk load time: ${entry.duration}ms`)
}
})
})
perfObserver.observe({ entryTypes: ['resource'] })
Error Boundary Handling
React error boundary component example:
class ErrorBoundary extends React.Component {
state = { hasError: false }
static getDerivedStateFromError() {
return { hasError: true }
}
componentDidCatch(error, info) {
logErrorToService(error, info.componentStack)
}
render() {
if (this.state.hasError) {
return <FallbackUI />
}
return this.props.children
}
}
// Usage
<ErrorBoundary>
<Suspense fallback={<Loader />}>
<AsyncComponent />
</Suspense>
</ErrorBoundary>
Mobile-Specific Optimizations
Differentiated loading strategies for mobile networks:
const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection
function loadAppropriateBundle() {
if (connection?.effectiveType === '4g') {
import('./full-feature')
} else {
import('./lite-version')
}
}
CSS Code Splitting Practices
Extracting critical CSS and asynchronously loading remaining styles:
// webpack.config.js
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = {
plugins: [
new MiniCssExtractPlugin({
filename: '[name].[contenthash].css',
chunkFilename: '[id].[contenthash].css'
})
],
module: {
rules: [
{
test: /\.css$/,
use: [
process.env.NODE_ENV === 'production'
? MiniCssExtractPlugin.loader
: 'style-loader',
'css-loader'
]
}
]
}
}
Splitting in Micro-Frontend Architecture
Module Federation configuration example:
// app1/webpack.config.js
new ModuleFederationPlugin({
name: 'app1',
filename: 'remoteEntry.js',
exposes: {
'./Nav': './src/components/Nav'
}
})
// app2/webpack.config.js
new ModuleFederationPlugin({
name: 'app2',
remotes: {
app1: 'app1@http://localhost:3001/remoteEntry.js'
}
})
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:Tree Shaking技术实现
下一篇:持久化缓存配置优化