Skip to content

feat(pack): add request-driven lazy compilation - #3298

Draft
fireairforce wants to merge 4 commits into
nextfrom
zoomdong/feat-lazy-dev-assets
Draft

feat(pack): add request-driven lazy compilation#3298
fireairforce wants to merge 4 commits into
nextfrom
zoomdong/feat-lazy-dev-assets

Conversation

@fireairforce

@fireairforce fireairforce commented Aug 11, 2026

Copy link
Copy Markdown
Member

Summary

Ant Design and other large dumi applications currently expand and write the complete development output graph before the dev server becomes ready. Even after output assets are served on demand, dynamic-import targets are still resolved, transformed, and analyzed while constructing the initial client graph.

This PR adds the opt-in devServer.lazyCompilation mode:

  • materialize development assets on browser requests through a persistent AssetGraphContentSource
  • skip eager writeToDisk, full output expansion, and endpoint-wide background watchers
  • defer client dynamic-import resolution and processing until its manifest chunk is requested
  • resolve HMR updates from the same per-resource VersionedContent
  • automatically enable dynamic HMR chunk lists and keep updates scoped to loaded imports
  • serve output.copy files from a separate request-driven source
  • keep existing eager behavior as the default, with eager fallback for stats/ANALYZE, library builds, Node targets, and server outputs

The Turbopack implementation is split into two small lifecycle changes:

Next.js-specific configuration, server, NAPI, and E2E code from the upstream draft are not included. Utoo activates the manifest directly through its generic development asset source.

Performance

Uncached Ant Design measurements on the same machine:

  • original eager startup: about 145.5s in the initial trace
  • request-driven output assets only: about 44.5–49.5s to ready
  • output assets plus deferred dynamic imports: about 19.6s to ready
  • cold process start to rendered homepage content: about 26.7s
  • subsequently opening the Button documentation route: about 2.18s

The latest cold run moved .turbopack and node_modules/.cache, used a fresh Chrome profile, and ran with the persistent filesystem cache disabled because the Ant Design checkout was dirty. Operating-system file pages may still be warm.

Known boundaries of this opt-in mode:

  • the first page pays for the dynamic modules it actually requests; Ant Design homepage content appeared about 7.1s after the server became ready
  • browser source-map requests work, but low-level Project.getSourceMap, getSourceMapSync, and traceSource still depend on the eager VersionedContentMap
  • after a lazy asset fails to compile, it may need to be requested again after the source is fixed to clear its global issue entry

Test Plan

  • cargo fmt --all --check
  • cargo check -p pack-napi
  • pack and pack-shared TypeScript checks
  • Biome checks for the integration fixture
  • lazy-compilation integration tests: 2/2 passed
    • dynamic loader is not invoked when the server becomes ready
    • entry and dynamic assets are served on demand
    • source map, HEAD and Range responses
    • copied assets and source updates
    • WebSocket partial HMR without full reload
    • fresh-process restart and forced-eviction behavior
  • Ant Design real-browser validation: homepage and Button documentation route rendered with no failed local requests

@fireairforce
fireairforce force-pushed the zoomdong/feat-lazy-dev-assets branch from 1856658 to 4a074a9 Compare August 11, 2026 18:28
@fireairforce
fireairforce force-pushed the zoomdong/feat-lazy-dev-assets branch from 1ef2fcc to bb1ebbb Compare August 13, 2026 09:21
@github-actions

Copy link
Copy Markdown

📊 Performance Benchmark Report (with-antd)

Utoopack Performance Report

Report ID: utoopack_performance_report_20260814_063005
Generated: 2026-08-14 06:30:05
Trace File: trace_antd.json (0.4GB, 1.13M spans)
Test Project: examples/with-antd


Executive Summary

Metric Value Assessment
Total Wall Time 7,767.4 ms Baseline
Total Thread Work (de-duped) 23,211.1 ms Non-overlapping busy time
Effective Parallelism 3.0x thread_work / wall_time
Working Threads 9 Threads with actual spans
Thread Utilization 33.2% ⚠️ Suboptimal
Total Spans 1,129,697 All B/E + X events
Meaningful Spans (>= 10us) 285,910 (25.3% of total)
Tracing Noise (< 10us) 843,787 (74.7% of total)

Build Phase Timeline

Shows when each build phase is active and how much CPU it consumes.
Self-Time is the time spent exclusively in that phase (excluding children).

Phase Spans Inclusive (ms) Self-Time (ms) Wall Range (ms)
Resolve 73,093 2,766.1 2,142.1 4,176.5
Parse 8,717 1,317.5 1,239.7 7,276.5
Analyze 183,160 14,962.4 10,756.3 7,200.3
Chunk 7,688 3,307.7 1,005.1 2,720.2
Codegen 8,387 1,887.3 1,761.4 2,284.2
Emit 35 41.1 20.6 7.7
Other 4,830 6,018.5 3,459.7 7,767.4

Workload Distribution by Diagnostic Tier

Category Spans Inclusive (ms) % Work Self-Time (ms) % Self
P0: Scheduling & Resolution 255,794 17,728.7 76.4% 12,897.0 55.6%
P1: I/O & Heavy Tasks 2,876 133.2 0.6% 112.6 0.5%
P2: Architecture (Locks/Memory) 0 0.0 0.0% 0.0 0.0%
P3: Asset Pipeline 23,147 6,521.1 28.1% 4,014.9 17.3%
P4: Bridge/Interop 0 0.0 0.0% 0.0 0.0%
Other 4,093 5,917.6 25.5% 3,360.4 14.5%

Top 20 Tasks by Self-Time

Self-time is the exclusive duration: time spent in the task itself, not in sub-tasks.
This is the most accurate indicator of where CPU cycles are actually spent.

Self (ms) Inclusive (ms) Count Avg Self (us) P95 Self (ms) Max Self (ms) % Work Task Name Top Caller
5,752.7 8,076.0 116,409 49.4 0.1 18.9 24.8% module create project (1%)
2,289.1 2,347.4 22,979 99.6 0.2 238.4 9.9% analyze ecmascript module module (67%)
1,909.5 1,932.2 25 76382.0 288.9 440.1 8.2% save snapshot persist (4%)
1,505.2 3,288.7 36,501 41.2 0.0 15.8 6.5% process module module (15%)
1,284.5 1,405.3 40,181 32.0 0.0 7.7 5.5% internal resolving resolving (30%)
1,157.0 1,234.7 6,549 176.7 0.6 68.1 5.0% parse ecmascript process module (29%)
1,030.4 1,030.4 4,887 210.8 0.8 141.3 4.4% compute async module info compute merged modules (0%)
1,004.0 1,004.0 6,226 161.3 0.6 9.2 4.3% precompute code generation generate merged code (58%)
848.3 1,351.5 32,239 26.3 0.0 5.8 3.7% resolving module (24%)
838.1 3,139.7 6,238 134.4 0.1 41.7 3.6% chunking compute async chunks (0%)
738.7 2,249.6 1,124 657.2 1.8 245.7 3.2% generate merged code chunking (79%)
466.2 466.2 329 1416.9 1.5 261.4 2.0% generate source map code generation (83%)
394.7 791.0 173 2281.4 4.4 213.3 1.7% emit code generate merged code (32%)
291.3 417.1 1,832 159.0 0.3 67.0 1.3% code generation chunking (11%)
271.5 432.7 2,712 100.1 0.0 187.0 1.2% create project None (0%)
163.3 164.3 1,395 117.1 0.2 18.3 0.7% compute async chunks create project (0%)
91.4 91.4 523 174.8 0.0 26.6 0.4% compute binding usage info create project (1%)
82.7 82.7 2,166 38.2 0.0 3.1 0.4% read file parse ecmascript (91%)
80.6 80.6 406 198.4 0.2 26.2 0.3% write all entrypoints to disk None (0%)
72.7 72.7 1,838 39.6 0.0 20.9 0.3% collect mergeable modules compute merged modules (0%)

Critical Path Analysis

The longest sequential dependency chains that determine wall-clock time.
Focus on reducing the depth of these chains to improve parallelism.

Rank Self-Time (ms) Depth Path
1 459.0 3 persist → save snapshot → blocking
2 459.0 3 chunking → generate merged code → emit code
3 307.8 4 chunking → generate merged code → emit code → generate source map
4 290.0 2 save snapshot → blocking
5 238.4 2 process module → analyze ecmascript module

Batching Candidates

High-volume tasks dominated by a single parent. If the parent can batch them,
it drastically reduces scheduler overhead.

Task Name Count Top Caller (Attribution) Avg Self P95 Self Total Self
No obvious batching candidates found - - - - -

Duration Distribution

Range Count Percentage
<10us 843,787 74.7%
10us-100us 266,974 23.6%
100us-1ms 13,813 1.2%
1ms-10ms 4,981 0.4%
10ms-100ms 123 0.0%
>100ms 19 0.0%

Action Items

  1. [P0] Focus on tasks with the highest Self-Time — these are where CPU cycles are actually spent.
  2. [P0] Use Batching Candidates to identify callers that should use try_join or reduce #[turbo_tasks::function] granularity.
  3. [P1] Check Build Phase Timeline for phases with disproportionate wall range vs. self-time (= serialization).
  4. [P1] Inspect P95 Self (ms) for heavy monolith tasks. Focus on long-tail outliers, not averages.
  5. [P1] Review Critical Paths — reducing the longest chain depth directly improves wall-clock time.
  6. [P2] If Thread Utilization < 60%, investigate scheduling gaps (lock contention or deep dependency chains).

Report generated by Utoopack Performance Analysis Agent

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant