代码分割

代码分割和懒加载是提高应用程序包大小和加载性能的强大技术。

  • 减少初始页面加载时需要加载的代码量
  • 代码按需加载,在需要时才加载
  • 结果是产生更多更小的块,这些块可以被浏览器更容易地缓存。

TanStack Router 如何进行代码分割?

TanStack Router 将代码分为两类

  • 关键路由配置 - 渲染当前路由和尽早启动数据加载过程所需代码。

    • 路径解析/序列化
    • 搜索参数验证
    • 加载器、加载前
    • 路由上下文
    • 静态数据
    • 链接
    • 脚本
    • 样式
    • 以下未列出的所有其他路由配置
  • 非关键/懒惰路由配置 - 匹配路由不需要的代码,可以按需加载。

    • 路由组件
    • 错误组件
    • 加载中组件
    • 未找到组件

🧠 为什么加载器不被分割?

  • 加载器本身已经是异步边界,所以您会为获取块等待加载器执行付出双倍的代价。

  • 从类别上讲,它比组件贡献大量包大小的可能性要小。

  • 加载器是路由最重要的可预加载资源之一,尤其是当您使用默认预加载意图时,例如将鼠标悬停在链接上,因此加载器能够无额外异步开销地可用非常重要。

    知道了分割加载器的缺点后,如果您仍然想继续,请前往 数据加载器分割 部分。

将路由的文件封装到一个目录中

由于 TanStack Router 的基于文件的路由系统旨在支持扁平化和嵌套文件结构,因此无需额外配置即可将路由的文件封装到单个目录中。

要将路由的文件封装到目录中,请将路由文件本身移到一个名为 .route 的文件中,该文件位于与路由文件名同名的目录中。

例如,如果您有一个名为 posts.tsx 的路由文件,您将创建一个名为 posts 的新目录,并将 posts.tsx 文件移入该目录,并将其重命名为 route.tsx

之前

  • posts.tsx

之后

  • posts
    • route.tsx

代码分割的几种方式

TanStack Router 支持多种代码分割方法。如果您正在使用基于文件的路由,请跳至 基于代码的分割 部分。

当您使用基于文件的路由时,可以使用以下代码分割方法

使用自动代码分割 ✨

这是分割路由文件最简单、最强大的方法。

当使用 autoCodeSplitting 功能时,TanStack Router 将根据上面提到的非关键路由配置自动分割您的路由文件。

重要

自动代码分割功能在使用我们 支持的打包器 进行文件路由时可用。如果您使用 CLI (@tanstack/router-cli),这将不起作用

要启用自动代码分割,您只需将以下内容添加到 TanStack Router 打包器插件的配置中

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

export default defineConfig({
  plugins: [
    tanstackRouter({
      // ...
      autoCodeSplitting: true,
    }),
    react(), // Make sure to add this plugin after the TanStack Router Bundler plugin
  ],
})
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { tanstackRouter } from '@tanstack/router-plugin/vite'

export default defineConfig({
  plugins: [
    tanstackRouter({
      // ...
      autoCodeSplitting: true,
    }),
    react(), // Make sure to add this plugin after the TanStack Router Bundler plugin
  ],
})

就是这样!TanStack Router 将根据其关键和非关键路由配置自动分割所有路由文件。

如果您想要更多地控制代码分割过程,请前往 自动代码分割 指南,了解更多可用选项。

使用 .lazy.tsx 后缀

如果您无法使用自动代码分割功能,您仍然可以使用 .lazy.tsx 后缀来分割您的路由文件。就像将代码移动到带有 .lazy.tsx 后缀的单独文件中,并使用 createLazyFileRoute 函数而不是 createFileRoute 一样简单。

重要

__root.tsx 路由文件,无论是使用 createRootRoute 还是 createRootRouteWithContext,都不支持代码分割,因为它始终会被渲染,无论当前路由是什么。

这些是 createLazyFileRoute 支持的唯一选项

导出名称描述
component为路由渲染的组件。
errorComponent在加载路由时发生错误时渲染的组件。
pendingComponent在路由加载时渲染的组件。
notFoundComponent如果抛出未找到错误,则渲染的组件。

使用 .lazy.tsx 进行代码分割的示例

当您使用 .lazy.tsx 时,您可以将路由分割成两个文件来实现代码分割

之前(单文件)

tsx
// src/routes/posts.tsx
import { createFileRoute } from '@tanstack/solid-router'
import { fetchPosts } from './api'

export const Route = createFileRoute('/posts')({
  loader: fetchPosts,
  component: Posts,
})

function Posts() {
  // ...
}
// src/routes/posts.tsx
import { createFileRoute } from '@tanstack/solid-router'
import { fetchPosts } from './api'

export const Route = createFileRoute('/posts')({
  loader: fetchPosts,
  component: Posts,
})

function Posts() {
  // ...
}

之后(分割成两个文件)

此文件将包含关键路由配置

tsx
// src/routes/posts.tsx

import { createFileRoute } from '@tanstack/solid-router'
import { fetchPosts } from './api'

export const Route = createFileRoute('/posts')({
  loader: fetchPosts,
})
// src/routes/posts.tsx

import { createFileRoute } from '@tanstack/solid-router'
import { fetchPosts } from './api'

export const Route = createFileRoute('/posts')({
  loader: fetchPosts,
})

而非关键路由配置将放入带有 .lazy.tsx 后缀的文件中

tsx
// src/routes/posts.lazy.tsx
import { createLazyFileRoute } from '@tanstack/solid-router'

export const Route = createLazyFileRoute('/posts')({
  component: Posts,
})

function Posts() {
  // ...
}
// src/routes/posts.lazy.tsx
import { createLazyFileRoute } from '@tanstack/solid-router'

export const Route = createLazyFileRoute('/posts')({
  component: Posts,
})

function Posts() {
  // ...
}

使用虚拟路由

您可能会遇到一种情况,即您将路由文件中的所有内容都分割出去了,导致路由文件为空!在这种情况下,只需删除该路由文件!将自动为您生成一个虚拟路由,作为代码分割文件的锚点。这个虚拟路由将直接存在于生成的路由树文件中。

之前(虚拟路由)

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

export const Route = createFileRoute('/posts')({
  // Hello?
})
// src/routes/posts.tsx
import { createFileRoute } from '@tanstack/solid-router'

export const Route = createFileRoute('/posts')({
  // Hello?
})
tsx
// src/routes/posts.lazy.tsx
import { createLazyFileRoute } from '@tanstack/solid-router'

export const Route = createLazyFileRoute('/posts')({
  component: Posts,
})

function Posts() {
  // ...
}
// src/routes/posts.lazy.tsx
import { createLazyFileRoute } from '@tanstack/solid-router'

export const Route = createLazyFileRoute('/posts')({
  component: Posts,
})

function Posts() {
  // ...
}

之后(虚拟路由)

tsx
// src/routes/posts.lazy.tsx
import { createLazyFileRoute } from '@tanstack/solid-router'

export const Route = createLazyFileRoute('/posts')({
  component: Posts,
})

function Posts() {
  // ...
}
// src/routes/posts.lazy.tsx
import { createLazyFileRoute } from '@tanstack/solid-router'

export const Route = createLazyFileRoute('/posts')({
  component: Posts,
})

function Posts() {
  // ...
}

搞定!🎉

基于代码的分割

如果您使用基于代码的路由,您仍然可以使用 Route.lazy() 方法和 createLazyRoute 函数来分割您的路由。您需要将路由配置分成两部分

使用 createLazyRoute 函数创建懒惰路由。

tsx
// src/posts.lazy.tsx
export const Route = createLazyRoute('/posts')({
  component: MyComponent,
})

function MyComponent() {
  return <div>My Component</div>
}
// src/posts.lazy.tsx
export const Route = createLazyRoute('/posts')({
  component: MyComponent,
})

function MyComponent() {
  return <div>My Component</div>
}

然后,在您的 app.tsx 文件中,调用路由定义上的 .lazy 方法来导入带有非关键路由配置的懒惰/代码分割路由。

tsx
// src/app.tsx
const postsRoute = createRoute({
  getParentRoute: () => rootRoute,
  path: '/posts',
}).lazy(() => import('./posts.lazy').then((d) => d.Route))
// src/app.tsx
const postsRoute = createRoute({
  getParentRoute: () => rootRoute,
  path: '/posts',
}).lazy(() => import('./posts.lazy').then((d) => d.Route))

数据加载器分割

请注意!!! 分割路由加载器是一场危险的游戏。

它可以成为减小包大小的强大工具,但正如 TanStack Router 如何进行代码分割? 部分所提到的,它是有代价的。

您可以使用路由的 loader 选项来代码分割您的数据加载逻辑。虽然这个过程使得与传递给加载器的参数保持类型安全变得困难,但您始终可以使用通用的 LoaderContext 类型来帮助您大部分工作。

tsx
import { lazyFn } from '@tanstack/solid-router'

const route = createRoute({
  path: '/my-route',
  component: MyComponent,
  loader: lazyFn(() => import('./loader'), 'loader'),
})

// In another file...a
export const loader = async (context: LoaderContext) => {
  /// ...
}
import { lazyFn } from '@tanstack/solid-router'

const route = createRoute({
  path: '/my-route',
  component: MyComponent,
  loader: lazyFn(() => import('./loader'), 'loader'),
})

// In another file...a
export const loader = async (context: LoaderContext) => {
  /// ...
}

如果您使用基于文件的路由,只有在使用 自动代码分割 并自定义打包器选项时,您才能分割您的 loader

使用 getRouteApi 辅助函数在其他文件中手动访问路由 API

正如您可能猜到的,将组件代码放在路由文件之外的单独文件中可能会导致在其他文件中难以使用该路由。为了解决这个问题,TanStack Router 导出了一个方便的 getRouteApi 函数,您可以使用它在不导入路由本身的文件中访问路由的类型安全 API。

  • my-route.tsx
tsx
import { createRoute } from '@tanstack/solid-router'
import { MyComponent } from './MyComponent'

const route = createRoute({
  path: '/my-route',
  loader: () => ({
    foo: 'bar',
  }),
  component: MyComponent,
})
import { createRoute } from '@tanstack/solid-router'
import { MyComponent } from './MyComponent'

const route = createRoute({
  path: '/my-route',
  loader: () => ({
    foo: 'bar',
  }),
  component: MyComponent,
})
  • MyComponent.tsx
tsx
import { getRouteApi } from '@tanstack/solid-router'

const route = getRouteApi('/my-route')

export function MyComponent() {
  const loaderData = route.useLoaderData()
  //    ^? { foo: string }

  return <div>...</div>
}
import { getRouteApi } from '@tanstack/solid-router'

const route = getRouteApi('/my-route')

export function MyComponent() {
  const loaderData = route.useLoaderData()
  //    ^? { foo: string }

  return <div>...</div>
}

getRouteApi 函数对于访问其他类型安全的 API 非常有用

  • useLoaderData
  • useLoaderDeps
  • useMatch
  • useParams
  • useRouteContext
  • useSearch
我们的合作伙伴
Code Rabbit
Netlify
Neon
Clerk
Convex
Sentry
订阅 Bytes

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

Bytes

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

订阅 Bytes

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

Bytes

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