阿里云主机折上折
  • 微信号
Current Site:Index > On-demand loading and lazy loading of routes

On-demand loading and lazy loading of routes

Author:Chuan Chen 阅读数:23814人阅读 分类: 构建工具

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:

  1. Entry Points: Manually split code using the entry configuration.
  2. Preventing Duplication: Use SplitChunksPlugin to deduplicate and separate chunks.
  3. 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

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.