Skip to content

Turbopack: turbofmt/turbobail macros#90092

Merged
mmastrac merged 11 commits intocanaryfrom
mmastrac/turbofmt
Mar 4, 2026
Merged

Turbopack: turbofmt/turbobail macros#90092
mmastrac merged 11 commits intocanaryfrom
mmastrac/turbofmt

Conversation

@mmastrac
Copy link
Contributor

@mmastrac mmastrac commented Feb 17, 2026

What

Add turbofmt! and turbobail! proc macros for async string formatting with Vc and ResolvedVc values.

A new, agent-friendly FORMATTING.md file is added to help agents figure out how these macros work.

Why

Formatting strings that include turbo-tasks values currently requires manually .await?ing each Vc, then calling format!, then converting to RcStr via .into(). By using macros, we can reduce a lot of boilerplate and potentially offer some opportunities for optimizations by reducing intermediate, throwaway tasks.

How

We make use of the "auto-deref" pattern in Rust to choose the "best" formatting option: ValueToString or Display.

  • Zero-alloc FormatIter: A shared iterator parser over format strings (RawString, EscapedBrace, VarRef, VarRefFormat) that both macros and the derive use, replacing duplicated manual parsing.
  • Borrow-based capture: turbofmt! uses async { .. } (not async move), so non-Copy types like FileSystemPath can be used in multiple turbofmt! calls without cloning.
  • Debug on StringifyType: Enables {:?} format specs in turbofmt!.
  • Converted ~30 call sites across next-core, turbo-tasks-fs, turbopack-core, turbopack-ecmascript, and others.

What's Left

  • Constants cannot be formatted with turbofmt! yet because of arcane Rust hygiene rules (similar issue to assert_eq! is not 100% hygienic rust-lang/rust#131446)
  • rust-analyzer doesn't show these format strings as "real" format strings yet because of limitations of the analyzer, but this should be possible to make work.
  • Automatic derive for ValueToStringRef to avoid intermediate tasks in a few places where formatting is trivial
  • turbowrite!/turbowriteln! could clean up a bit more code

@nextjs-bot nextjs-bot added created-by: Turbopack team PRs by the Turbopack team. Turbopack Related to Turbopack with Next.js. labels Feb 17, 2026
Copy link
Contributor Author

mmastrac commented Feb 17, 2026

This stack of pull requests is managed by Graphite. Learn more about stacking.

@codspeed-hq
Copy link

codspeed-hq bot commented Feb 17, 2026

Merging this PR will not alter performance

✅ 17 untouched benchmarks
⏩ 3 skipped benchmarks1


Comparing mmastrac/turbofmt (5287b4e) with canary (5719439)

Open in CodSpeed

Footnotes

  1. 3 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@mmastrac mmastrac changed the title turbofmt/turbobail Turbopack: turbofmt/turbobail macros Feb 19, 2026
@nextjs-bot
Copy link
Collaborator

nextjs-bot commented Feb 19, 2026

Tests Passed

@nextjs-bot
Copy link
Collaborator

nextjs-bot commented Feb 19, 2026

Stats from current PR

✅ No significant changes detected

📊 All Metrics
📖 Metrics Glossary

Dev Server Metrics:

  • Listen = TCP port starts accepting connections
  • First Request = HTTP server returns successful response
  • Cold = Fresh build (no cache)
  • Warm = With cached build artifacts

Build Metrics:

  • Fresh = Clean build (no .next directory)
  • Cached = With existing .next directory

Change Thresholds:

  • Time: Changes < 50ms AND < 10%, OR < 2% are insignificant
  • Size: Changes < 1KB AND < 1% are insignificant
  • All other changes are flagged to catch regressions

⚡ Dev Server

Metric Canary PR Change Trend
Cold (Listen) 507ms 507ms ▁▃▁▇█
Cold (Ready in log) 492ms 492ms ▂▂▁▇█
Cold (First Request) 1.076s 1.075s ▃▃▁▇█
Warm (Listen) 508ms 508ms ▁▃▁▇█
Warm (Ready in log) 489ms 492ms ▁▃▁▇█
Warm (First Request) 396ms 405ms ▁▃▁▇█
📦 Dev Server (Webpack) (Legacy)

📦 Dev Server (Webpack)

Metric Canary PR Change Trend
Cold (Listen) 507ms 507ms ▁▁▅▁▁
Cold (Ready in log) 468ms 472ms ▃▁▅▁▄
Cold (First Request) 2.145s 2.154s ▂▁▅▁▂
Warm (Listen) 508ms 507ms ▁▁▅▁▁
Warm (Ready in log) 467ms 469ms ▃▁▅▁▃
Warm (First Request) 2.155s 2.162s ▂▁▅▁▂

⚡ Production Builds

Metric Canary PR Change Trend
Fresh Build 4.621s 4.664s ▁▄▁▆█
Cached Build 4.634s 4.680s ▁▄▁▆█
📦 Production Builds (Webpack) (Legacy)

📦 Production Builds (Webpack)

Metric Canary PR Change Trend
Fresh Build 15.661s 15.604s ▁▁▄▁▁
Cached Build 15.646s 15.767s ▁▁▄▁▁
node_modules Size 476 MB 476 MB ▁▁▁▁▁
📦 Bundle Sizes

Bundle Sizes

⚡ Turbopack

Client

Main Bundles: **401 kB** → **401 kB** ✅ -30 B

80 files with content-based hashes (individual files not comparable between builds)

Server

Middleware
Canary PR Change
middleware-b..fest.js gzip 770 B 762 B 🟢 8 B (-1%)
Total 770 B 762 B ✅ -8 B
Build Details
Build Manifests
Canary PR Change
_buildManifest.js gzip 450 B 452 B
Total 450 B 452 B ⚠️ +2 B

📦 Webpack

Client

Main Bundles
Canary PR Change
5528-HASH.js gzip 5.54 kB N/A -
6280-HASH.js gzip 58.8 kB N/A -
6335.HASH.js gzip 169 B N/A -
912-HASH.js gzip 4.59 kB N/A -
e8aec2e4-HASH.js gzip 62.6 kB N/A -
framework-HASH.js gzip 59.7 kB 59.7 kB
main-app-HASH.js gzip 256 B 254 B
main-HASH.js gzip 39.1 kB 39.1 kB
webpack-HASH.js gzip 1.68 kB 1.68 kB
262-HASH.js gzip N/A 4.59 kB -
2889.HASH.js gzip N/A 169 B -
5602-HASH.js gzip N/A 5.55 kB -
6948ada0-HASH.js gzip N/A 62.6 kB -
9544-HASH.js gzip N/A 59.5 kB -
Total 233 kB 233 kB ⚠️ +697 B
Polyfills
Canary PR Change
polyfills-HASH.js gzip 39.4 kB 39.4 kB
Total 39.4 kB 39.4 kB
Pages
Canary PR Change
_app-HASH.js gzip 194 B 194 B
_error-HASH.js gzip 183 B 180 B 🟢 3 B (-2%)
css-HASH.js gzip 331 B 330 B
dynamic-HASH.js gzip 1.81 kB 1.81 kB
edge-ssr-HASH.js gzip 256 B 256 B
head-HASH.js gzip 351 B 352 B
hooks-HASH.js gzip 384 B 383 B
image-HASH.js gzip 580 B 581 B
index-HASH.js gzip 260 B 260 B
link-HASH.js gzip 2.51 kB 2.51 kB
routerDirect..HASH.js gzip 320 B 319 B
script-HASH.js gzip 386 B 386 B
withRouter-HASH.js gzip 315 B 315 B
1afbb74e6ecf..834.css gzip 106 B 106 B
Total 7.98 kB 7.98 kB ✅ -1 B

Server

Edge SSR
Canary PR Change
edge-ssr.js gzip 125 kB 125 kB
page.js gzip 254 kB 255 kB
Total 379 kB 380 kB ⚠️ +725 B
Middleware
Canary PR Change
middleware-b..fest.js gzip 617 B 616 B
middleware-r..fest.js gzip 156 B 155 B
middleware.js gzip 43.8 kB 43.9 kB
edge-runtime..pack.js gzip 842 B 842 B
Total 45.4 kB 45.5 kB ⚠️ +60 B
Build Details
Build Manifests
Canary PR Change
_buildManifest.js gzip 715 B 718 B
Total 715 B 718 B ⚠️ +3 B
Build Cache
Canary PR Change
0.pack gzip 4.05 MB 4.06 MB 🔴 +10 kB (+0%)
index.pack gzip 104 kB 103 kB 🟢 1.26 kB (-1%)
index.pack.old gzip 102 kB 103 kB 🔴 +1.11 kB (+1%)
Total 4.26 MB 4.27 MB ⚠️ +9.85 kB

🔄 Shared (bundler-independent)

Runtimes
Canary PR Change
app-page-exp...dev.js gzip 321 kB 321 kB
app-page-exp..prod.js gzip 170 kB 170 kB
app-page-tur...dev.js gzip 320 kB 320 kB
app-page-tur..prod.js gzip 170 kB 170 kB
app-page-tur...dev.js gzip 317 kB 317 kB
app-page-tur..prod.js gzip 168 kB 168 kB
app-page.run...dev.js gzip 317 kB 317 kB
app-page.run..prod.js gzip 168 kB 168 kB
app-route-ex...dev.js gzip 70.8 kB 70.8 kB
app-route-ex..prod.js gzip 49.3 kB 49.3 kB
app-route-tu...dev.js gzip 70.9 kB 70.9 kB
app-route-tu..prod.js gzip 49.3 kB 49.3 kB
app-route-tu...dev.js gzip 70.5 kB 70.5 kB
app-route-tu..prod.js gzip 49 kB 49 kB
app-route.ru...dev.js gzip 70.4 kB 70.4 kB
app-route.ru..prod.js gzip 49 kB 49 kB
dist_client_...dev.js gzip 324 B 324 B
dist_client_...dev.js gzip 326 B 326 B
dist_client_...dev.js gzip 318 B 318 B
dist_client_...dev.js gzip 317 B 317 B
pages-api-tu...dev.js gzip 43.2 kB 43.2 kB
pages-api-tu..prod.js gzip 32.9 kB 32.9 kB
pages-api.ru...dev.js gzip 43.2 kB 43.2 kB
pages-api.ru..prod.js gzip 32.9 kB 32.9 kB
pages-turbo....dev.js gzip 52.6 kB 52.6 kB
pages-turbo...prod.js gzip 38.5 kB 38.5 kB
pages.runtim...dev.js gzip 52.6 kB 52.6 kB
pages.runtim..prod.js gzip 38.5 kB 38.5 kB
server.runti..prod.js gzip 62 kB 62 kB
Total 2.83 MB 2.83 MB ⚠️ +2 B
📎 Tarball URL
https://vercel-packages.vercel.app/next/commits/5287b4e62990fd901074d84fff750e8745bc0db9/next

@mmastrac mmastrac force-pushed the mmastrac/turbofmt branch 4 times, most recently from f3a0484 to 2b20af3 Compare February 24, 2026 21:19
@nextjs-bot nextjs-bot added the Font (next/font) Related to Next.js Font Optimization. label Feb 24, 2026
@mmastrac mmastrac marked this pull request as ready for review February 24, 2026 22:06
@lukesandberg lukesandberg self-requested a review February 25, 2026 01:04
}
RealPathResultError::NotFound => {
format!("Symlink {orig} is invalid, it points at a file that doesn't exist")
turbofmt!("Symlink {orig} is invalid, it points at a file that doesn't exist")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so here we are actually changing the error message to include the filesystem name.... since these are always about actual filesystem paths... does that make sense

// Happens if the link is to a non-existent file
FileSystemEntryType::NotFound => DirectoryEntry::Error(
format!("Symlink {symlink} points at {real_path} which does not exist").into(),
turbofmt!("Symlink {symlink} points at {real_path} which does not exist")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think all of the other errors in this file are using fs-prefixed paths, but I can revert this one if it makes sense (I suspect it just add consistency)

path
} else {
bail!("chunk path {chunk_path} is not in output root {output_root}");
turbobail!("chunk path {chunk_path} is not in output root {output_root}");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure it is correct to change this

@mmastrac mmastrac force-pushed the mmastrac/turbofmt branch 2 times, most recently from 21d8d31 to d70683c Compare March 2, 2026 16:26
@mmastrac mmastrac enabled auto-merge (squash) March 4, 2026 14:48
@mmastrac mmastrac merged commit e4cae37 into canary Mar 4, 2026
162 checks passed
@mmastrac mmastrac deleted the mmastrac/turbofmt branch March 4, 2026 15:11
sokra pushed a commit that referenced this pull request Mar 6, 2026
# What
Add turbofmt! and turbobail! proc macros for async string formatting
with Vc<T> and ResolvedVc<T> values.

A new, agent-friendly `FORMATTING.md` file is added to help agents
figure out how these macros work.

# Why

Formatting strings that include turbo-tasks values currently requires
manually .await?ing each Vc, then calling format!, then converting to
RcStr via .into(). By using macros, we can reduce a lot of boilerplate
and potentially offer some opportunities for optimizations by reducing
intermediate, throwaway tasks.

# How

We make use of the "auto-deref" pattern in Rust to choose the "best"
formatting option: ValueToString or Display.

- Zero-alloc FormatIter: A shared iterator parser over format strings
(RawString, EscapedBrace, VarRef, VarRefFormat) that both macros and the
derive use, replacing duplicated manual parsing.
- Borrow-based capture: turbofmt! uses async { .. } (not async move), so
non-Copy types like FileSystemPath can be used in multiple turbofmt!
calls without cloning.
  - Debug on StringifyType: Enables {:?} format specs in turbofmt!.
- Converted ~30 call sites across next-core, turbo-tasks-fs,
turbopack-core, turbopack-ecmascript, and others.

# What's Left

- Constants cannot be formatted with turbofmt! yet because of arcane
Rust hygiene rules (similar issue to
rust-lang/rust#131446)
- rust-analyzer doesn't show these format strings as "real" format
strings yet because of limitations of the analyzer, but this should be
possible to make work.
- Automatic derive for ValueToStringRef to avoid intermediate tasks in a
few places where formatting is trivial
 -  turbowrite!/turbowriteln! could clean up a bit more code
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

created-by: Turbopack team PRs by the Turbopack team. Font (next/font) Related to Next.js Font Optimization. Turbopack Related to Turbopack with Next.js.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants