自动代码分割

TanStack Router 中的自动代码分割功能通过惰性加载路由组件及其相关数据来优化应用程序的捆绑包大小。这对于大型应用程序特别有用,您可以通过仅加载当前路由所需的代码来最小化初始加载时间。

要启用此功能,只需在捆绑器插件配置中将 autoCodeSplitting 选项设置为 true。这样,路由器就可以自动处理路由的代码分割,而无需任何额外的设置。

ts
// vite.config.ts
import { defineConfig } from 'vite'
import { tanstackRouter } from '@tanstack/router-plugin/vite'

export default defineConfig({
  plugins: [
    tanstackRouter({
      autoCodeSplitting: true, // Enable automatic code splitting
    }),
  ],
})
// vite.config.ts
import { defineConfig } from 'vite'
import { tanstackRouter } from '@tanstack/router-plugin/vite'

export default defineConfig({
  plugins: [
    tanstackRouter({
      autoCodeSplitting: true, // Enable automatic code splitting
    }),
  ],
})

但这仅仅是开始!TanStack Router 的自动代码分割不仅易于启用,而且还提供了强大的自定义选项,可以定制您的路由如何分割成块。这使您可以根据特定的需求和使用模式来优化应用程序的性能。

它是如何工作的?

TanStack Router 的自动代码分割通过在“开发”和“构建”阶段转换您的路由文件来工作。它重写了路由定义,以使用组件和加载器的惰性加载包装器,这允许捆绑器将这些属性分组到单独的块中。

提示

块 (chunk) 是包含应用程序代码一部分的文件,可以按需加载。这有助于通过仅加载应用程序当前路由所需的代码来减少初始加载时间。

因此,当您的应用程序加载时,它不会包含所有路由的所有代码。而是仅包含最初需要的路由的代码。随着用户在应用程序中导航,其他块会按需加载。

这一切都是无缝发生的,无需您手动分割代码或管理惰性加载。TanStack Router 捆绑器插件会处理所有事情,确保您的路由开箱即用,针对性能进行了优化。

转换过程

当您启用自动代码分割时,捆绑器插件会使用静态代码分析来查看路由文件中的代码,并将其转换为优化的输出。

此转换过程在处理每个路由文件时会产生两个关键输出:

  1. 引用文件 (Reference File):捆绑器插件会获取您原始的路由文件(例如 posts.route.tsx),并修改 componentpendingComponent 等属性的值,以使用特殊的惰性加载包装器,这些包装器稍后会获取实际代码。这些包装器指向一个“虚拟”文件,捆绑器稍后会解析它。
  2. 虚拟文件 (Virtual File):当捆绑器看到对这些虚拟文件之一(例如 posts.route.tsx?tsr-split=component)的请求时,它会拦截该请求,以生成一个新、最小的即时文件,该文件包含请求属性的代码(例如,仅 PostsComponent)。

此过程可确保您的原始代码保持干净和易读,而实际的捆绑输出则针对初始捆绑包大小进行了优化。

哪些代码会被分割?

将什么分割成单独的块的决定对于优化应用程序的性能至关重要。TanStack Router 使用称为“分割分组 (Split Groupings)”的概念来确定您路由的不同部分应如何捆绑在一起。

分割分组是属性的数组,它告诉 TanStack Router 如何将路由的不同部分捆绑在一起。每个分组是您希望捆绑在一起成为单个惰性加载块的属性名称列表。

可分割的属性有:

  • component
  • errorComponent
  • pendingComponent
  • notFoundComponent
  • loader

默认情况下,TanStack Router 使用以下分割分组:

sh
[
  ['component'],
  ['errorComponent'],
  ['notFoundComponent']
]
[
  ['component'],
  ['errorComponent'],
  ['notFoundComponent']
]

这意味着它为每个路由创建三个单独的惰性加载块。结果是:

  • 一个用于主组件
  • 一个用于错误组件
  • 还有一个用于未找到组件。

精细化控制

对于大多数应用程序,默认的 autoCodeSplitting: true 行为就足够了。然而,TanStack Router 提供了多种选项来自定义您的路由如何分割成块,从而使您可以针对特定用例或性能需求进行优化。

全局代码分割行为 (defaultBehavior)

您可以通过更改捆绑器插件配置中的 defaultBehavior 选项来更改 TanStack Router 分割路由的方式。这使您可以定义路由的不同属性如何捆绑在一起。

例如,要将所有 UI 相关组件捆绑到一个块中,您可以这样配置:

ts
// vite.config.ts
import { defineConfig } from 'vite'
import { tanstackRouter } from '@tanstack/router-plugin/vite'

export default defineConfig({
  plugins: [
    tanstackRouter({
      autoCodeSplitting: true,
      codeSplittingOptions: {
        defaultBehavior: [
          [
            'component',
            'pendingComponent',
            'errorComponent',
            'notFoundComponent',
          ], // Bundle all UI components together
        ],
      },
    }),
  ],
})
// vite.config.ts
import { defineConfig } from 'vite'
import { tanstackRouter } from '@tanstack/router-plugin/vite'

export default defineConfig({
  plugins: [
    tanstackRouter({
      autoCodeSplitting: true,
      codeSplittingOptions: {
        defaultBehavior: [
          [
            'component',
            'pendingComponent',
            'errorComponent',
            'notFoundComponent',
          ], // Bundle all UI components together
        ],
      },
    }),
  ],
})

高级程序化控制 (splitBehavior)

对于复杂的规则集,您可以使用 vite 配置中的 splitBehavior 函数,以程序化的方式定义路由应根据其 routeId 分割成块。此函数允许您实现自定义逻辑来组合属性,从而为您提供对代码分割行为的细粒度控制。

ts
// vite.config.ts
import { defineConfig } from 'vite'
import { tanstackRouter } from '@tanstack/router-plugin/vite'

export default defineConfig({
  plugins: [
    tanstackRouter({
      autoCodeSplitting: true,
      codeSplittingOptions: {
        splitBehavior: ({ routeId }) => {
          // For all routes under /posts, bundle the loader and component together
          if (routeId.startsWith('/posts')) {
            return [['loader', 'component']]
          }
          // All other routes will use the `defaultBehavior`
        },
      },
    }),
  ],
})
// vite.config.ts
import { defineConfig } from 'vite'
import { tanstackRouter } from '@tanstack/router-plugin/vite'

export default defineConfig({
  plugins: [
    tanstackRouter({
      autoCodeSplitting: true,
      codeSplittingOptions: {
        splitBehavior: ({ routeId }) => {
          // For all routes under /posts, bundle the loader and component together
          if (routeId.startsWith('/posts')) {
            return [['loader', 'component']]
          }
          // All other routes will use the `defaultBehavior`
        },
      },
    }),
  ],
})

按路由覆盖 (codeSplitGroupings)

为了获得最终的控制权,您可以在路由文件中直接覆盖全局配置,方法是添加一个 codeSplitGroupings 属性。这对于具有独特优化需求的路由非常有用。

tsx
// src/routes/posts.route.tsx
import { createFileRoute } from '@tanstack/solid-router'
import { loadPostsData } from './-heavy-posts-utils'

export const Route = createFileRoute('/posts')({
  // For this specific route, bundle the loader and component together.
  codeSplitGroupings: [['loader', 'component']],
  loader: () => loadPostsData(),
  component: PostsComponent,
})

function PostsComponent() {
  // ...
}
// src/routes/posts.route.tsx
import { createFileRoute } from '@tanstack/solid-router'
import { loadPostsData } from './-heavy-posts-utils'

export const Route = createFileRoute('/posts')({
  // For this specific route, bundle the loader and component together.
  codeSplitGroupings: [['loader', 'component']],
  loader: () => loadPostsData(),
  component: PostsComponent,
})

function PostsComponent() {
  // ...
}

这将创建一个包含该特定路由的 loadercomponent 的单个块,从而覆盖了默认行为以及捆绑器配置中定义的任何程序化拆分行为。

配置顺序很重要

本指南到目前为止描述了三种不同的方式来配置 TanStack Router 如何将路由分割成块。

为确保不同配置之间不发生冲突,TanStack Router 使用以下优先级顺序:

  1. 按路由覆盖:路由文件内的 codeSplitGroupings 属性具有最高的优先级。这允许您为单个路由定义特定的分割分组。
  2. 程序化拆分行为:捆绑器配置中的 splitBehavior 函数允许您根据路由的 routeId 定义自定义逻辑来分割路由。
  3. 默认行为:捆绑器配置中的 defaultBehavior 选项作为未定义特定覆盖或自定义逻辑的路由的回退。这是适用于所有路由的基本配置,除非被覆盖。

分割数据加载器

loader 函数负责获取路由所需的数据。默认情况下,它与“引用文件”一起捆绑并在初始捆绑包中加载。但是,您也可以将 loader 分割成自己的块,如果您想进一步优化的话。

注意

loader 移动到自己的块中是一项性能权衡。它会在获取数据之前引入一次额外的服务器往返,这可能导致页面初始加载速度变慢。这是因为 loader 必须在路由渲染其组件之前被获取并执行。因此,除非您有特定原因要将其分割,否则我们建议将 loader 保留在初始捆绑包中。

ts
// vite.config.ts
import { defineConfig } from 'vite'
import { tanstackRouter } from '@tanstack/router-plugin/vite'

export default defineConfig({
  plugins: [
    tanstackRouter({
      autoCodeSplitting: true,
      codeSplittingOptions: {
        defaultBehavior: [
          ['loader'], // The loader will be in its own chunk
          ['component'],
          // ... other component groupings
        ],
      },
    }),
  ],
})
// vite.config.ts
import { defineConfig } from 'vite'
import { tanstackRouter } from '@tanstack/router-plugin/vite'

export default defineConfig({
  plugins: [
    tanstackRouter({
      autoCodeSplitting: true,
      codeSplittingOptions: {
        defaultBehavior: [
          ['loader'], // The loader will be in its own chunk
          ['component'],
          // ... other component groupings
        ],
      },
    }),
  ],
})

我们强烈建议不要分割 loader,除非您有特定的用例需要这样做。在大多数情况下,不将 loader 分离并将其保留在主捆绑包中是性能的最佳选择。

我们的合作伙伴
Code Rabbit
Netlify
Neon
Clerk
Convex
Sentry
订阅 Bytes

您的每周 JavaScript 资讯。每周一免费发送给超过 10 万开发者。

Bytes

无垃圾邮件。您可以随时取消订阅。

订阅 Bytes

您的每周 JavaScript 资讯。每周一免费发送给超过 10 万开发者。

Bytes

无垃圾邮件。您可以随时取消订阅。