mcpField
C
code-splitting

code-splitting

github.com/tanstack开发15.3k201 次安装4 天前更新
代码仓库

关于 code-splitting

面向 TanStack Router 开发者,介绍如何将路由中的关键代码与可延迟加载的组件拆分。涵盖自动和手动拆分,以及避免破坏拆分效果的导入方式。

主要功能

  1. 01自动与手动代码拆分
  2. 02区分关键路由代码与延迟加载组件
  3. 03使用 getRouteApi 访问类型化钩子
  4. 04按路由配置拆分分组
  5. 05避免导出组件导致其进入主包

适用场景

  1. 01为路由组件启用延迟加载
  2. 02在无法使用打包插件时手动拆分路由
  3. 03从拆分文件调用类型化路由钩子

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/tanstack/router --skill code-splitting

安装后在 Claude Code 中输入/code-splitting确认已加载。

详细介绍

代码拆分

TanStack Router 将路由代码分为关键代码(匹配路由并开始加载所必需)和非关键代码(可延迟加载)。打包工具插件可以自动拆分,也可以使用 .lazy.tsx 文件手动拆分。

CRITICAL:切勿从路由文件中 export 组件函数——导出的函数会被包含在主包中,完全绕过代码拆分。

CRITICAL:在代码拆分文件中使用 getRouteApi('/path'),而非 import { Route } from './route'。导入 Route 会破坏代码拆分。

主包中保留的内容(关键代码)

  • 路径解析/序列化
  • validateSearch
  • loader、beforeLoad
  • 路由上下文、静态数据
  • 链接、脚本、样式

拆分的内容(非关键代码)

  • component
  • errorComponent
  • pendingComponent
  • notFoundComponent

默认情况下不会拆分 loader。它本身已经是异步的,因此拆分会增加双重异步开销:先获取代码块,再执行 loader。只有在有特定原因时才拆分 loader。

设置:自动代码拆分

在打包工具插件中启用 autoCodeSplitting: true。这是推荐的做法。

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

export default defineConfig({
  plugins: [
    // TanStack Router plugin MUST come before the framework plugin
    tanstackRouter({
      autoCodeSplitting: true,
    }),
    react(),
  ],
})

启用后,路由文件会自动转换。组件会拆分到单独的代码块中;loader 则保留在主包中。无需创建 .lazy.tsx 文件。

// src/routes/posts.tsx — everything in one file, splitting is automatic
import { createFileRoute } from '@tanstack/react-router'
import { fetchPosts } from '../api'

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

// NOT exported — this is critical for automatic code splitting to work
function PostsComponent() {
  const posts = Route.useLoaderData()
  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  )
}

使用 .lazy.tsx 手动拆分

如果无法使用自动代码拆分(例如,仅使用 CLI,没有打包工具插件),请手动拆分为两个文件:

// src/routes/posts.tsx — critical route config only
import { createFileRoute } from '@tanstack/react-router'
import { fetchPosts } from '../api'

export const Route = createFileRoute('/posts')({
  loader: fetchPosts,
})
// src/routes/posts.lazy.tsx — non-critical (lazy-loaded)
import { createLazyFileRoute } from '@tanstack/react-router'

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

function PostsComponent() {
  // Use getRouteApi to access typed hooks without importing Route
  return <div>Posts</div>
}

createLazyFileRoute 仅支持:component、errorComponent、pendingComponent、notFoundComponent。

虚拟路由

如果拆分后关键路由文件为空,请将其完全删除。routeTree.gen.ts 中会自动生成虚拟路由:

// src/routes/about.lazy.tsx — no about.tsx needed
import { createLazyFileRoute } from '@tanstack/react-router'

export const Route = createLazyFileRoute('/about')({
  component: () => <h1>About Us</h1>,
})

基于代码的拆分

对于基于代码(非基于文件)的路由,请使用 createLazyRoute 和 .lazy() 方法:

// src/posts.lazy.tsx
import { createLazyRoute } from '@tanstack/react-router'

export const Route = createLazyRoute('/posts')({
  component: PostsComponent,
})

function PostsComponent() {
  return <div>Posts</div>
}
// src/app.tsx
import { createRoute } from '@tanstack/react-router'

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

在拆分文件中访问类型化 Hook:getRouteApi

当组件位于单独的文件中时,请使用 getRouteApi,无需导入 Route 对象即可获得对路由 Hook 的类型化访问:

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

const routeApi = getRouteApi('/posts')

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

function PostsComponent() {
  const posts = routeApi.useLoaderData()
  const { page } = routeApi.useSearch()
  const params = routeApi.useParams()
  const context = routeApi.useRouteContext()
  return <div>Posts page {page}</div>
}

getRouteApi 提供:useLoaderData、useLoaderDeps、useMatch、useParams、useRouteContext、useSearch。

按路由覆盖拆分设置:codeSplitGroupings

在路由文件中直接添加 codeSplitGroupings,即可覆盖特定路由的拆分行为:

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

export const Route = createFileRoute('/posts')({
  // Bundle loader and component together for this route
  codeSplitGroupings: [['loader', 'component']],
  loader: () => loadPostsData(),
  component: PostsComponent,
})

function PostsComponent() {
  const data = Route.useLoaderData()
  return <div>{data.title}</div>
}

全局拆分配置

defaultBehavior — 更改默认分组
// vite.config.ts
import { defineConfig } from 'vite'
import { tanstackRouter } from '@tanstack/router-plugin/vite'

export default defineConfig({
  plugins: [
    tanstackRouter({
      autoCodeSplitting: true,
      codeSplittingOptions: {
        defaultBehavior: [
          // Bundle all UI components into one chunk
          [
            'component',
            'pendingComponent',
            'errorComponent',
            'notFoundComponent',
          ],
        ],
      },
    }),
  ],
})
splitBehavior — 以编程方式设置按路由逻辑
// vite.config.ts
import { defineConfig } from 'vite'
import { tanstackRouter } from '@tanstack/router-plugin/vite'

export default defineConfig({
  plugins: [
    tanstackRouter({
      autoCodeSplitting: true,
      codeSplittingOptions: {
        splitBehavior: ({ routeId }) => {
          if (routeId.startsWith('/posts')) {
            return [['loader', 'component']]
          }
          // All other routes use defaultBehavior
        },
      },
    }),
  ],
})
优先级顺序
  1. 按路由设置的 codeSplitGroupings(最高)
  2. splitBehavior 函数
  3. defaultBehavior 选项(最低)

常见错误

1. 高:导出组件函数会阻止代码拆分
// WRONG — export puts PostsComponent in the main bundle
export function PostsComponent() {
  return <div>Posts</div>
}

// CORRECT — no export, function stays in the split chunk
function PostsComponent() {
  return <div>Posts</div>
}
2. 中:尝试拆分根路由

__root.tsx 不支持代码拆分。无论当前路由是什么,它始终都会渲染。不要创建 __root.lazy.tsx。

3. 中:拆分 loader 会增加双重异步开销
// AVOID unless you have a specific reason
codeSplittingOptions: {
  defaultBehavior: [
    ['loader'], // Fetch chunk THEN execute loader = two network waterfalls
    ['component'],
  ],
}

// PREFERRED — loader stays in main bundle (default behavior)
codeSplittingOptions: {
  defaultBehavior: [
    ['component'],
    ['errorComponent'],
    ['notFoundComponent'],
  ],
}
4. 高:在代码拆分文件中导入 Route 以访问类型化 Hook
// WRONG — importing Route pulls route config into the lazy chunk
import { Route } from './posts.tsx'
const data = Route.useLoaderData()

// CORRECT — getRouteApi gives typed hooks without pulling in the route
import { getRouteApi } from '@tanstack/react-router'
const routeApi = getRouteApi('/posts')
const data = routeApi.useLoaderData()

交叉引用

  • router-core/data-loading — loader 拆分决策会影响数据加载性能。拆分 loader 会增加获取数据前的延迟。
  • router-core/type-safety — getRouteApi 是从拆分文件访问 Hook 的类型安全方式。

评论

支持 Markdown