12k
All articles

如何在 Svelte 中懒加载组件

通过动态导入和条件渲染实现 Svelte 组件的懒加载,在 SvelteKit 和基于 Vite 的项目中保持初始打包体积精简。

OpenReplay Team
OpenReplay Team
如何在 Svelte 中懒加载组件

你的 Svelte 应用可能默认就很快,但如果你将富文本编辑器、图表库或复杂的仪表板组件打包到初始 JavaScript 负载中,用户就会下载他们可能永远不会使用的代码。Svelte 中的组件级懒加载通过延迟这些重型导入直到真正需要时才加载来解决这个问题。

与 React 不同,Svelte 没有内置的 lazy() 辅助函数。相反,懒加载直接依赖于 JavaScript 原生的 import() 函数结合条件渲染。这种方式更手动,但也更灵活。

核心要点

  • Svelte 缺少内置的 lazy() 辅助函数,因此懒加载使用 JavaScript 原生的 import() 配合条件渲染
  • SvelteKit 自动处理路由级代码分割,但对于单个页面内的重型组件,仍然需要组件级懒加载
  • Vite 零配置即可将动态导入拆分为独立的代码块
  • 三种核心模式涵盖了大多数使用场景:按需加载(使用 {#await})、悬停时预加载、以及延迟到浏览器空闲时加载

为什么组件级懒加载在 SvelteKit 中仍然重要

SvelteKit 已经自动处理路由级代码分割。每个路由都有自己的代码块,因此在页面之间导航不会使初始包体积膨胀。但在单个页面内,你在 .svelte 文件顶部导入的所有内容都会被打包在一起并预先加载。

这就是组件级懒加载变得有价值的地方。如果页面包含重型图表组件、视频播放器或仅在用户交互后才出现的模态框,就没有理由在页面到达时加载这些代码。

SvelteKit 使用 Vite 构建,它原生支持动态导入。当 Vite 看到 import('./HeavyChart.svelte') 时,它会自动将该模块拆分为单独的代码块。无需额外配置。

如何懒加载 Svelte 组件:核心模式

使用 {#await} 进行基本懒加载

最简单的方法是直接在模板中使用 Svelte 的 {#await} 块:

<!-- src/routes/+page.svelte -->
<script>
  let showChart = false;
  let chartData = [1, 2, 3];
</script>

<button onclick={() => showChart = true}>加载图表</button>

{#if showChart}
  {#await import('$lib/components/HeavyChart.svelte')}
    <p>加载图表中...</p>
  {:then Chart}
    <Chart.default data={chartData} />
  {:catch error}
    <p>加载图表失败: {error.message}</p>
  {/await}
{/if}

这在 Svelte 4 和 Svelte 5 中都有效。{:catch} 块很重要——网络故障会发生,而静默错误会使调试变得痛苦。在 Svelte 4 中,动态切换组件通常使用 <svelte:component this={Component}>。在现代 Svelte 中,更改组件引用也可以直接触发重新渲染。

浏览器在首次导入后会缓存该模块。同一组件的后续渲染不会触发新的网络请求。

悬停时加载以提升感知性能

一个常见的模式是在用户发出意图信号时开始加载——悬停在触发元素上:

<!-- src/routes/+page.svelte -->
<script lang="ts">
  import type { Component } from 'svelte';

  let HeavyWidget: Component<{ message: string }> | null = $state(null);

  async function loadWidget() {
    if (!HeavyWidget) {
      const module = await import('$lib/components/HeavyWidget.svelte');
      HeavyWidget = module.default;
    }
  }
</script>

<div onmouseenter={loadWidget}>
  <p>悬停以预加载组件</p>
</div>

{#if HeavyWidget}
  <HeavyWidget message="准备就绪!" />
{/if}

这对于工具提示、弹出框和侧边栏效果很好。组件在用户点击之前就开始加载,因此当他们交互时,通常已经被缓存了。

浏览器空闲时加载

对于改善页面但不是立即需要的非关键组件,使用 requestIdleCallback:

<script lang="ts">
  import { onMount } from 'svelte';
  import type { Component } from 'svelte';

  let FeedbackWidget: Component | null = $state(null);

  onMount(() => {
    const load = () =>
      import('$lib/components/FeedbackWidget.svelte').then(
        (m) => (FeedbackWidget = m.default)
      );

    if ('requestIdleCallback' in window) {
      requestIdleCallback(load);
    } else {
      setTimeout(load, 300); // Safari 的降级方案
    }
  });
</script>

{#if FeedbackWidget}
  <FeedbackWidget />
{/if}

请注意,Safari 对 requestIdleCallback 的支持历来不一致,因此建议使用 setTimeout 作为降级方案。

何时不应该懒加载

并非每个组件都能从懒加载中受益。以下情况应避免使用:

  • 首屏 UI — 导航、主视觉区、主要内容
  • 小型组件 — 异步开销超过了节省的资源
  • 挂载时立即需要的组件 — 加载闪烁会降低用户体验

过度拆分会创建许多小的异步代码块。像 Vite 这样的现代打包工具能高效处理这种情况,但仍然存在收益递减的临界点。

结论

懒加载 Svelte 组件归结为一件事:使用 import() 而不是静态导入,然后有条件地渲染结果。SvelteKit 基于 Vite 的构建会自动处理代码分割。上述三种模式——按需、悬停时和空闲时——涵盖了大多数实际场景。选择与用户实际需要组件时机相匹配的触发方式,添加错误处理,你的初始包就能保持精简。

常见问题

懒加载在 Svelte 4 中有效还是仅在 Svelte 5 中有效?

动态导入在两个版本中都有效。在 Svelte 4 中,你通常使用带有 this 属性的 svelte:component 来渲染加载的组件。在 Svelte 5 中,你可以直接在模板中将解析的组件作为标签使用。两种情况下底层的导入机制是相同的。

我可以在 SvelteKit 的服务端渲染期间懒加载组件吗?

动态导入在 SSR 期间也会执行,因为 Node.js 支持它们。然而,懒加载主要是一种客户端优化,旨在减少浏览器的初始 JavaScript 负载。如果组件必须在服务器上渲染以满足 SEO 或首次绘制的需求,静态导入是更好的选择。

我如何知道哪些组件值得懒加载?

检查你的构建输出或使用打包分析器来识别大型代码块。引入重型第三方库(如图表渲染器、富文本编辑器或地图组件)的组件是强有力的候选对象。如果一个组件增加的体积少于几千字节,额外网络请求的开销可能会超过节省的资源。

懒加载的组件每次渲染时都会闪现加载状态吗?

不会。浏览器在首次动态导入解析后会缓存该模块。在后续渲染中,缓存的模块会立即返回,因此加载状态仅在初始获取时出现。你还可以在悬停时或空闲时预加载模块,以完全消除可见的延迟。

DevTools for the frontend

Gain Debugging Superpowers

Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers.

Star on GitHub12k

We use cookies to improve your experience. By using our site, you accept cookies.