| title | Tree-Shaking | ||||||
|---|---|---|---|---|---|---|---|
| id | tree-shaking | ||||||
| order | 7 | ||||||
| description | TanStack AI's tree-shakeable architecture — import only the activities and adapters you use for minimal bundle size across chat, image, and speech. | ||||||
| keywords |
|
TanStack AI is designed from the ground up for maximum tree-shakeability. The entire system—from activity functions to adapters—uses a functional, modular architecture that ensures you only bundle the code you actually use.
Instead of a monolithic API that includes everything, TanStack AI provides:
- Individual activity functions - Import only the activities you need (
chat,summarize, etc.) - Individual adapter functions - Import only the adapters you need (
openaiText,openaiSummarize, etc.) - Functional API design - Pure functions that can be easily eliminated by bundlers
- Separate modules - Each activity and adapter lives in its own module
This design means that if you only use chat with OpenAI, you won't bundle code for summarization, image generation, or other providers.
Each AI activity is exported as a separate function from @tanstack/ai:
// Import only the activities you need
import { chat } from '@tanstack/ai' // Chat/text generation
import { summarize } from '@tanstack/ai' // Summarization
import { generateImage } from '@tanstack/ai' // Image generation
import { generateSpeech } from '@tanstack/ai' // Text-to-speech
import { generateTranscription } from '@tanstack/ai' // Audio transcription
import { generateVideo } from '@tanstack/ai' // Video generationIf you only need chat functionality:
// Only chat code is bundled
import { chat } from '@tanstack/ai'
import { openaiText } from '@tanstack/ai-openai'
const stream = chat({
adapter: openaiText('gpt-5.2'),
messages: [{ role: 'user', content: 'Hello!' }],
})Your bundle will not include:
- Summarization logic
- Image generation logic
- Other activity implementations
Each provider package exports individual adapter functions for each activity type:
import {
openaiText, // Chat/text generation
openaiSummarize, // Summarization
openaiImage, // Image generation
openaiSpeech, // Text-to-speech
openaiTranscription, // Audio transcription
openaiVideo, // Video generation
} from '@tanstack/ai-openai'import {
anthropicText, // Chat/text generation
anthropicSummarize, // Summarization
} from '@tanstack/ai-anthropic'import {
geminiText, // Chat/text generation
geminiSummarize, // Summarization
geminiImage, // Image generation
geminiSpeech, // Text-to-speech (experimental)
} from '@tanstack/ai-gemini'import {
ollamaText, // Chat/text generation
ollamaSummarize, // Summarization
} from '@tanstack/ai-ollama'Here's how the tree-shakeable design works in practice:
// Only import what you need
import { chat } from '@tanstack/ai'
import { openaiText } from '@tanstack/ai-openai'
// Chat generation - returns AsyncIterable<StreamChunk>
const chatResult = chat({
adapter: openaiText('gpt-5.2'),
messages: [{ role: 'user', content: 'Hello!' }],
})
for await (const chunk of chatResult) {
console.log(chunk)
}What gets bundled:
- ✅
chatfunction and its dependencies - ✅
openaiTextadapter and its dependencies - ✅ Chat-specific streaming and tool handling logic
What doesn't get bundled:
- ❌
summarizefunction - ❌
generateImagefunction - ❌ Other adapter implementations (Anthropic, Gemini, etc.)
- ❌ Other activity implementations
If you need multiple activities, import only what you use:
import { chat, summarize } from '@tanstack/ai'
import {
openaiText,
openaiSummarize
} from '@tanstack/ai-openai'
// Each activity is independent
const chatResult = chat({
adapter: openaiText('gpt-5.2'),
messages: [{ role: 'user', content: 'Hello!' }],
})
const summarizeResult = await summarize({
adapter: openaiSummarize('gpt-5-mini'),
text: 'Long text to summarize...',
})Each activity is in its own module, so bundlers can eliminate unused ones.
The tree-shakeable design doesn't sacrifice type safety. Each adapter provides full type safety for its supported models:
import { openaiText, type OpenAIChatModel } from '@tanstack/ai-openai'
const adapter = openaiText()
// TypeScript knows the exact models supported
const model: OpenAIChatModel = 'gpt-5.2' // ✓ Valid
const model2: OpenAIChatModel = 'invalid' // ✗ Type errorThe create___Options functions are also tree-shakeable:
import {
createChatOptions,
createImageOptions
} from '@tanstack/ai'
// Only import what you need
const chatOptions = createChatOptions({
adapter: openaiText('gpt-5.2'),
})The functional, modular design provides significant bundle size benefits:
// ❌ Importing more than needed
import * as ai from '@tanstack/ai'
import * as openai from '@tanstack/ai-openai'
// This bundles all exports from both packages// ✅ Only what you use gets bundled
import { chat } from '@tanstack/ai'
import { openaiText } from '@tanstack/ai-openai'
// You only get:
// - Chat activity implementation
// - OpenAI text adapter
// - Chat-specific dependenciesFor a typical chat application:
- Monolithic approach: ~200KB+ (all activities + all adapters)
- Tree-shakeable approach: ~50KB (only chat + one adapter)
That's a 75% reduction in bundle size for most applications!
The tree-shakeability is achieved through:
- ES Module exports - Each function is a named export, not a default export
- Separate modules - Each activity and adapter lives in its own file
- No side effects - Functions are pure and don't have module-level side effects
- Functional composition - Functions compose together, allowing dead code elimination
- Type-only imports - Type imports are stripped at build time
Modern bundlers (Vite, Webpack, Rollup, esbuild) can easily eliminate unused code because:
- Functions are statically analyzable
- No dynamic imports of unused code
- No module-level side effects
- Clear dependency graphs
- Import only what you need - Don't import entire namespaces
- Use specific adapter functions - Import
openaiTextnotopenai - Separate activities by route - Different API routes can use different activities
- Lazy load when possible - Use dynamic imports for code-split routes
- Keep mobile chat bundles client-only - React Native and Expo chat screens
should import
useChatand chat connection adapters, not provider SDKs, server response helpers, React DOM UI, devtools UI, or other framework packages. See Quick Start: React Native for the server-only provider boundary and mobile transport setup.
// ✅ Good - Only imports chat
import { chat } from '@tanstack/ai'
import { openaiText } from '@tanstack/ai-openai'
// ❌ Bad - Imports everything
import * as ai from '@tanstack/ai'
import * as openai from '@tanstack/ai-openai'Each adapter type implements a specific interface:
ChatAdapter- ProvideschatStream()method for streaming chat responsesSummarizeAdapter- Providessummarize()method for text summarizationImageAdapter- ProvidesgenerateImage()method for image generationTTSAdapter- ProvidesgenerateSpeech()method for text-to-speechTranscriptionAdapter- ProvidesgenerateTranscription()method for audio transcriptionVideoAdapter- ProvidesgenerateVideo()method for video generation
All adapters have a kind property that indicates their type:
const chatAdapter = openaiText()
console.log(chatAdapter.kind) // 'text'
const summarizeAdapter = openaiSummarize()
console.log(summarizeAdapter.kind) // 'summarize'TanStack AI's tree-shakeable design means:
- ✅ Smaller bundles - Only include code you actually use
- ✅ Faster load times - Less JavaScript to download and parse
- ✅ Better performance - Less code means faster execution
- ✅ Type safety - Full TypeScript support without runtime overhead
- ✅ Flexibility - Mix and match activities and adapters as needed
The functional, modular architecture ensures that modern bundlers can eliminate unused code effectively, resulting in optimal bundle sizes for your application.