Turbopack 核心特性 #
增量编译 #
什么是增量编译? #
增量编译是指只编译发生变化的部分,而不是每次都重新编译整个项目。Turbopack 通过增量编译实现了极致的构建速度。
text
┌─────────────────────────────────────────────────────────────┐
│ 增量编译原理 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 传统编译: │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ 全部文件 │ → │ 全部编译 │ → │ 全部输出 │ │
│ └─────────┘ └─────────┘ └─────────┘ │
│ 时间: O(n) │
│ │
│ 增量编译: │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ 变化文件 │ → │ 增量编译 │ → │ 增量输出 │ │
│ └─────────┘ └─────────┘ └─────────┘ │
│ 时间: O(1) │
│ │
└─────────────────────────────────────────────────────────────┘
变化检测 #
Turbopack 使用多种方式检测文件变化:
rust
pub struct FileWatcher {
watcher: RecommendedWatcher,
cache: Arc<Cache>,
}
impl FileWatcher {
pub fn detect_changes(&self) -> Vec<Change> {
let mut changes = Vec::new();
for event in self.watcher.events() {
match event.kind {
EventKind::Create(_) => changes.push(Change::Added),
EventKind::Modify(_) => changes.push(Change::Modified),
EventKind::Remove(_) => changes.push(Change::Deleted),
_ => {}
}
}
changes
}
}
增量编译流程 #
text
┌─────────────────────────────────────────────────────────────┐
│ 增量编译流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 文件变化检测 │
│ └── 监听文件系统事件 │
│ │
│ 2. 依赖图更新 │
│ └── 更新受影响的模块依赖 │
│ │
│ 3. 缓存失效 │
│ └── 标记需要重新编译的模块 │
│ │
│ 4. 增量编译 │
│ └── 只编译变化的模块 │
│ │
│ 5. 缓存更新 │
│ └── 保存新的编译结果 │
│ │
│ 6. 输出更新 │
│ └── 生成新的输出文件 │
│ │
└─────────────────────────────────────────────────────────────┘
实际效果 #
javascript
// 修改前: src/utils/format.ts
export function formatDate(date: Date): string {
return date.toLocaleDateString();
}
// 修改后: src/utils/format.ts
export function formatDate(date: Date): string {
return date.toLocaleDateString('zh-CN');
}
// Turbopack 只重新编译:
// 1. src/utils/format.ts
// 2. 导入 format.ts 的文件(增量更新)
// 其他文件使用缓存
持久化缓存 #
缓存架构 #
text
┌─────────────────────────────────────────────────────────────┐
│ 缓存层级 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ L1: 内存缓存 │ │
│ │ - 最快访问速度 │ │
│ │ - 当前会话有效 │ │
│ │ - 自动管理内存 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ L2: 磁盘缓存 │ │
│ │ - 持久化存储 │ │
│ │ - 跨会话有效 │ │
│ │ - 增量更新 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ L3: 远程缓存 │ │
│ │ - 团队共享 │ │
│ │ - CI/CD 集成 │ │
│ │ - 按需下载 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
缓存结构 #
text
.turbo/
├── cache/
│ ├── analysis/
│ │ ├── a1b2c3d4.json
│ │ └── e5f6g7h8.json
│ ├── emit/
│ │ ├── output-a1b2c3d4.js
│ │ └── output-e5f6g7h8.js
│ └── source-maps/
│ ├── output-a1b2c3d4.js.map
│ └── output-e5f6g7h8.js.map
└── info.json
缓存键计算 #
rust
pub struct CacheKey {
content_hash: String,
dependencies: Vec<String>,
config_hash: String,
env_hash: String,
}
impl CacheKey {
pub fn compute(source: &str, config: &Config) -> Self {
let content_hash = sha256(source);
let dependencies = resolve_dependencies(source);
let config_hash = sha256(&config.to_string());
let env_hash = sha256(&env::vars().collect::<String>());
Self {
content_hash,
dependencies,
config_hash,
env_hash,
}
}
}
缓存命中 #
rust
pub fn get_or_compile(key: &CacheKey) -> Result<CompiledModule> {
if let Some(cached) = memory_cache.get(key) {
return Ok(cached);
}
if let Some(cached) = disk_cache.get(key) {
memory_cache.insert(key.clone(), cached.clone());
return Ok(cached);
}
let compiled = compile(key)?;
memory_cache.insert(key.clone(), compiled.clone());
disk_cache.insert(key.clone(), compiled.clone());
Ok(compiled)
}
缓存失效策略 #
rust
pub enum InvalidationReason {
SourceChanged,
DependencyChanged,
ConfigChanged,
EnvChanged,
CacheExpired,
}
pub fn check_invalidation(key: &CacheKey) -> Option<InvalidationReason> {
if source_changed(key) {
return Some(InvalidationReason::SourceChanged);
}
if dependency_changed(key) {
return Some(InvalidationReason::DependencyChanged);
}
if config_changed(key) {
return Some(InvalidationReason::ConfigChanged);
}
None
}
函数级缓存 #
为什么是函数级? #
传统打包器使用文件级缓存,但 Turbopack 实现了更细粒度的函数级缓存:
text
┌─────────────────────────────────────────────────────────────┐
│ 缓存粒度对比 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 文件级缓存: │
│ ┌─────────────────────────────────────┐ │
│ │ function a() { } │ │
│ │ function b() { } ← 修改这里 │ │
│ │ function c() { } │ │
│ └─────────────────────────────────────┘ │
│ 结果: 整个文件需要重新编译 │
│ │
│ 函数级缓存: │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ function a()│ │ function b()│ │ function c()│ │
│ │ [cached] │ │ [recompile] │ │ [cached] │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ 结果: 只重新编译函数 b │
│ │
└─────────────────────────────────────────────────────────────┘
函数级缓存实现 #
rust
pub struct FunctionCache {
functions: HashMap<FunctionId, CachedFunction>,
}
pub struct CachedFunction {
id: FunctionId,
source: String,
hash: String,
compiled: Vec<u8>,
dependencies: Vec<FunctionId>,
}
impl FunctionCache {
pub fn get_or_compile(&mut self, func: &Function) -> Result<&CachedFunction> {
let hash = self.compute_hash(func);
if let Some(cached) = self.functions.get(&func.id) {
if cached.hash == hash {
return Ok(cached);
}
}
let compiled = self.compile_function(func)?;
self.functions.insert(func.id.clone(), compiled);
Ok(self.functions.get(&func.id).unwrap())
}
}
实际效果 #
typescript
// utils.ts
export function formatPrice(price: number): string {
return `$${price.toFixed(2)}`;
}
export function formatDate(date: Date): string {
return date.toLocaleDateString();
}
export function capitalize(str: string): string {
return str.charAt(0).toUpperCase() + str.slice(1);
}
如果只修改 formatDate:
text
编译结果:
├── formatPrice: [使用缓存]
├── formatDate: [重新编译]
└── capitalize: [使用缓存]
懒加载编译 #
懒加载原理 #
Turbopack 只编译当前请求需要的代码:
text
┌─────────────────────────────────────────────────────────────┐
│ 懒加载编译流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 浏览器请求: /page-a │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 只编译 page-a 相关模块 │ │
│ │ ├── page-a.tsx │ │
│ │ ├── components/Header.tsx │ │
│ │ └── utils/format.ts │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 其他页面: /page-b, /page-c │
│ │ │
│ └── 不编译,等待请求时再编译 │
│ │
└─────────────────────────────────────────────────────────────┘
请求驱动编译 #
rust
pub struct LazyCompiler {
module_graph: ModuleGraph,
compiled: HashSet<ModuleId>,
}
impl LazyCompiler {
pub async fn compile_for_request(
&mut self,
request_path: &str,
) -> Result<Vec<CompiledModule>> {
let entry = self.module_graph.resolve_entry(request_path)?;
let mut result = Vec::new();
for module_id in self.module_graph.dependencies_of(entry) {
if !self.compiled.contains(&module_id) {
let compiled = self.compile_module(&module_id).await?;
result.push(compiled);
self.compiled.insert(module_id);
}
}
Ok(result)
}
}
按需编译示例 #
text
项目结构:
src/
├── pages/
│ ├── home.tsx # 访问 / 时编译
│ ├── about.tsx # 访问 /about 时编译
│ ├── contact.tsx # 访问 /contact 时编译
│ └── blog/
│ ├── index.tsx # 访问 /blog 时编译
│ └── [slug].tsx
└── components/
├── Header.tsx # 被 home.tsx 引用时编译
└── Footer.tsx
启动时:
- 只编译入口文件
- 其他文件等待请求
访问 /about 时:
- 编译 about.tsx
- 编译 about.tsx 的依赖
- 其他页面不编译
性能对比 #
text
┌─────────────────────────────────────────────────────────────┐
│ 冷启动时间对比 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Webpack (全量编译): │
│ ████████████████████████████████████████ 30s │
│ │
│ Vite (按需编译): │
│ ████████ 2.5s │
│ │
│ Turbopack (懒加载编译): │
│ █ 0.5s │
│ │
└─────────────────────────────────────────────────────────────┘
Rust 高性能实现 #
为什么选择 Rust? #
text
┌─────────────────────────────────────────────────────────────┐
│ Rust 优势 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 性能 │
│ ├── 零成本抽象 │
│ ├── 无 GC 停顿 │
│ └── 接近 C/C++ 性能 │
│ │
│ 2. 并发 │
│ ├── 无数据竞争 │
│ ├── 安全的多线程 │
│ └── 高效的异步运行时 │
│ │
│ 3. 内存安全 │
│ ├── 编译时检查 │
│ ├── 无空指针 │
│ └── 无缓冲区溢出 │
│ │
│ 4. 工具链 │
│ ├── Cargo 包管理 │
│ ├── 内置测试 │
│ └── 优秀的文档 │
│ │
└─────────────────────────────────────────────────────────────┘
核心数据结构 #
rust
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;
pub struct ModuleGraph {
modules: Arc<RwLock<HashMap<ModuleId, Module>>>,
edges: Vec<Edge>,
cache: PersistentCache,
}
pub struct Module {
id: ModuleId,
source: Source,
ast: Option<AST>,
dependencies: Vec<ModuleId>,
compiled: Option<CompiledOutput>,
}
pub struct Edge {
from: ModuleId,
to: ModuleId,
kind: DependencyKind,
}
并行编译 #
rust
use rayon::prelude::*;
impl ModuleGraph {
pub fn compile_parallel(&mut self) -> Result<Vec<CompiledModule>> {
let modules: Vec<&mut Module> = self.modules
.values_mut()
.collect();
modules
.par_iter_mut()
.map(|module| self.compile_module(module))
.collect()
}
}
异步 I/O #
rust
use tokio::fs;
use tokio::io::AsyncReadExt;
pub async fn read_source(path: &Path) -> Result<String> {
let mut file = fs::File::open(path).await?;
let mut contents = String::new();
file.read_to_string(&mut contents).await?;
Ok(contents)
}
pub async fn compile_module(path: &Path) -> Result<CompiledModule> {
let source = read_source(path).await?;
let ast = parse(&source)?;
let output = transform(&ast)?;
Ok(CompiledModule { source, ast, output })
}
内存管理 #
rust
pub struct MemoryPool {
chunks: Vec<Box<[u8]>>,
current: usize,
}
impl MemoryPool {
pub fn allocate(&mut self, size: usize) -> &mut [u8] {
if self.current + size > self.chunks.len() {
self.chunks.push(vec![0; size.max(1024 * 1024)].into_boxed_slice());
}
let start = self.current;
self.current += size;
&mut self.chunks.last_mut().unwrap()[start..self.current]
}
}
HMR 热更新 #
HMR 架构 #
text
┌─────────────────────────────────────────────────────────────┐
│ HMR 流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 文件变化 │
│ │ │
│ ▼ │
│ ┌─────────────┐ │
│ │ 文件监听器 │ │
│ └──────┬──────┘ │
│ │ │
│ ▼ │
│ ┌─────────────┐ │
│ │ 增量编译 │ │
│ └──────┬──────┘ │
│ │ │
│ ▼ │
│ ┌─────────────┐ │
│ │ HMR 服务器 │ │
│ └──────┬──────┘ │
│ │ │
│ ▼ │
│ ┌─────────────┐ │
│ │ WebSocket │ │
│ └──────┬──────┘ │
│ │ │
│ ▼ │
│ ┌─────────────┐ │
│ │ 浏览器更新 │ │
│ └─────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
HMR 实现 #
rust
pub struct HmrServer {
clients: Vec<WebSocket>,
watcher: FileWatcher,
compiler: Compiler,
}
impl HmrServer {
pub async fn handle_change(&mut self, change: FileChange) -> Result<()> {
let compiled = self.compiler.compile_incremental(&change).await?;
let update = HmrUpdate {
modules: compiled.modules,
hash: compiled.hash,
};
for client in &self.clients {
client.send(serde_json::to_string(&update)?).await?;
}
Ok(())
}
}
客户端 HMR #
typescript
// hmr-client.ts
interface HmrUpdate {
modules: string[];
hash: string;
}
const socket = new WebSocket('ws://localhost:3000/_turbo/hmr');
socket.onmessage = (event) => {
const update: HmrUpdate = JSON.parse(event.data);
update.modules.forEach((moduleId) => {
const module = __webpack_modules__[moduleId];
if (module && module.hot) {
module.hot.accept();
}
});
console.log('[HMR] Updated modules:', update.modules);
};
HMR 性能 #
text
┌─────────────────────────────────────────────────────────────┐
│ HMR 时间对比 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 项目规模: 3000 模块 │
│ │
│ Webpack: │
│ ████████████████████████████████ 3000ms │
│ │
│ Vite: │
│ ████████ 200ms │
│ │
│ Turbopack: │
│ █ 10ms │
│ │
└─────────────────────────────────────────────────────────────┘
Tree Shaking #
Tree Shaking 原理 #
text
┌─────────────────────────────────────────────────────────────┐
│ Tree Shaking │
├─────────────────────────────────────────────────────────────┤
│ │
│ 源代码: │
│ ┌─────────────────────────────────────┐ │
│ │ export function used() { } │ │
│ │ export function unused() { } │ │
│ │ export function alsoUnused() { } │ │
│ └─────────────────────────────────────┘ │
│ │
│ 导入: │
│ import { used } from './utils'; │
│ │
│ 打包结果: │
│ ┌─────────────────────────────────────┐ │
│ │ function used() { } │ │
│ └─────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
静态分析 #
rust
pub struct TreeShaker {
used_exports: HashSet<ExportId>,
}
impl TreeShaker {
pub fn analyze(&mut self, entry: &ModuleId, graph: &ModuleGraph) {
let mut stack = vec![entry];
while let Some(module_id) = stack.pop() {
let module = graph.get(module_id);
for import in &module.imports {
self.used_exports.insert(import.export_id.clone());
stack.push(&import.module_id);
}
}
}
pub fn shake(&self, module: &Module) -> ShakedModule {
let kept_functions: Vec<_> = module.functions
.iter()
.filter(|f| self.used_exports.contains(&f.export_id))
.collect();
ShakedModule {
functions: kept_functions,
}
}
}
副作用标记 #
json
// package.json
{
"name": "my-library",
"sideEffects": false
}
json
// 或指定有副作用的文件
{
"sideEffects": [
"*.css",
"*.scss",
"./src/polyfills.js"
]
}
Source Map #
Source Map 生成 #
rust
pub struct SourceMapGenerator {
sources: Vec<String>,
names: Vec<String>,
mappings: Vec<Mapping>,
}
impl SourceMapGenerator {
pub fn add_mapping(
&mut self,
generated: Position,
original: Position,
source: &str,
name: Option<&str>,
) {
self.sources.push(source.to_string());
if let Some(n) = name {
self.names.push(n.to_string());
}
self.mappings.push(Mapping {
generated,
original,
source_index: self.sources.len() - 1,
name_index: name.map(|_| self.names.len() - 1),
});
}
pub fn to_json(&self) -> String {
serde_json::to_string(&SourceMapJson {
version: 3,
sources: &self.sources,
names: &self.names,
mappings: &self.encode_mappings(),
}).unwrap()
}
}
Source Map 配置 #
javascript
// next.config.js
module.exports = {
experimental: {
turbo: {
sourceMaps: true,
sourceMapOptions: {
exclude: /node_modules/,
include: /src/,
},
},
},
}
下一步 #
现在你已经深入了解了 Turbopack 的核心特性,接下来学习 迁移指南 将现有项目迁移到 Turbopack!
最后更新:2026-03-28