Hone logo
Hone
Problems

Vue.js Lazy Loading for Components

This challenge focuses on implementing lazy loading for Vue.js components using TypeScript. Lazy loading is a performance optimization technique that defers the loading of certain resources until they are actually needed, significantly improving initial page load times, especially in large applications. You will create a system that dynamically imports and renders components only when they are visible in the viewport.

Problem Description

Your task is to create a reusable Vue.js component that implements lazy loading for other components. This "LazyLoader" component will act as a wrapper. When the LazyLoader component enters the viewport, it should dynamically import and render the specified component.

Key Requirements:

  1. Dynamic Import: Use defineAsyncComponent from Vue 3 to dynamically import the target component.
  2. Intersection Observer: Utilize the IntersectionObserver API to detect when the LazyLoader component becomes visible within the viewport.
  3. Fallback/Loading State: Provide a mechanism to display a loading indicator (e.g., a simple text message or a placeholder component) while the target component is being fetched.
  4. Error Handling: Implement basic error handling for failed component imports. Display an error message if the component cannot be loaded.
  5. Props: The LazyLoader component should accept props to specify:
    • The factory function for defineAsyncComponent (e.g., () => import('./MyComponent.vue')).
    • An optional loadingComponent prop for a custom loading indicator.
    • An optional errorComponent prop for a custom error indicator.
    • Any props to be passed to the lazily loaded component.

Expected Behavior:

  • Initially, only the LazyLoader component itself is rendered, potentially displaying a loading state.
  • When the LazyLoader component scrolls into the viewport:
    • The specified component is dynamically imported.
    • If loadingComponent is provided, it should be displayed during the import process.
    • Once the component is loaded, it should replace the loading indicator and be rendered.
    • If the import fails, the errorComponent (or a default error message) should be displayed.
  • The LazyLoader should only attempt to load the component once.

Edge Cases:

  • The target component might fail to load due to network issues or incorrect paths.
  • The LazyLoader might be rendered initially in the viewport.
  • The user might scroll rapidly, causing multiple intersection events.

Examples

Example 1: Basic Lazy Loading

src/components/MyHeavyComponent.vue

<template>
  <div>
    <h1>This is a heavy component!</h1>
    <p>It was loaded lazily.</p>
  </div>
</template>

src/components/LazyLoader.vue (Implementation details to be provided by the user)

src/App.vue

<template>
  <div>
    <div style="height: 150vh;">
      Scroll down to see the lazy-loaded component...
    </div>
    <LazyLoader :componentLoader="() => import('./components/MyHeavyComponent.vue')" />
    <div style="height: 50vh;">
      Content below lazy-loaded component.
    </div>
  </div>
</template>

<script lang="ts">
import { defineComponent } from 'vue';
import LazyLoader from './components/LazyLoader.vue';

export default defineComponent({
  components: {
    LazyLoader,
  },
});
</script>

Expected Output (after scrolling down):

The MyHeavyComponent will be rendered after it enters the viewport. Before it's loaded, a loading indicator will be shown (or nothing, if no loading indicator is specified).

Example 2: With Loading and Error Components

src/components/LoadingSpinner.vue

<template>
  <div class="spinner">Loading...</div>
</template>
<style>
.spinner { font-weight: bold; color: blue; }
</style>

src/components/ErrorDisplay.vue

<template>
  <div class="error">Failed to load component!</div>
</template>
<style>
.error { font-weight: bold; color: red; }
</style>

src/App.vue

<template>
  <div>
    <div style="height: 150vh;">
      Scroll down to see the lazy-loaded component...
    </div>
    <LazyLoader
      :componentLoader="() => import('./components/MyHeavyComponent.vue')"
      :loadingComponent="LoadingSpinner"
      :errorComponent="ErrorDisplay"
    />
    <div style="height: 50vh;">
      Content below lazy-loaded component.
    </div>
  </div>
</template>

<script lang="ts">
import { defineComponent } from 'vue';
import LazyLoader from './components/LazyLoader.vue';
import LoadingSpinner from './components/LoadingSpinner.vue';
import ErrorDisplay from './components/ErrorDisplay.vue';

export default defineComponent({
  components: {
    LazyLoader,
    LoadingSpinner, // Registered globally or imported here
    ErrorDisplay,   // Registered globally or imported here
  },
  setup() {
    return {
      LoadingSpinner,
      ErrorDisplay,
    };
  }
});
</script>

Expected Output (after scrolling down):

Initially, LoadingSpinner is shown. If MyHeavyComponent loads successfully, it replaces the spinner. If it fails, ErrorDisplay is shown.

Constraints

  • The solution must be implemented in TypeScript.
  • Vue 3 is the target framework.
  • The LazyLoader component should be a functional component or a standard Vue component.
  • Avoid using third-party libraries for the core lazy loading and intersection observer logic.
  • The componentLoader prop should accept a function that returns a Promise that resolves to a component definition.

Notes

  • Consider using ref and onMounted in Vue's Composition API for managing the Intersection Observer.
  • defineAsyncComponent is your primary tool for the dynamic import part.
  • Think about the lifecycle of the LazyLoader component and when to set up and tear down the Intersection Observer.
  • Passing props to the lazily loaded component should be handled by the LazyLoader. You can use v-bind="$attrs" or explicitly pass props.
Loading editor...
typescript