mirror of
https://github.com/hanzoai/next.js.git
synced 2026-09-24 17:00:51 +00:00
### What? Two fixes for the Turbopack build tracing introduced in #90397: 1. **Don't block SSG on Turbopack shutdown**: `workerMain()` no longer awaits the shutdown promise before returning. Trace event collection is deferred to `waitForShutdown()`, which the parent process awaits *after* SSG completes. This allows static generation and Turbopack persistence/cache-flush to run in parallel. 2. **Add persistence spans to `trace-build` allowlist**: `turbopack-build-events`, `turbopack-persistence`, and `turbopack-compaction` are now included in the `to-json-build.ts` allowlist so they appear in `.next/trace-build`. ### Why? - The `await shutdownPromise` in `workerMain()` was too eager — it prevented the caller from acknowledging the build as complete and starting SSG until Turbopack persistence finished flushing to disk. - The persistence/compaction spans emitted by Rust (`turbopack-persistence`, `turbopack-compaction`) were not in the `to-json-build.ts` allowlist, so they were silently filtered out of `.next/trace-build`. ### How? **`impl.ts` (worker)**: - Removed `await shutdownPromise` from `workerMain()` — it now returns build results immediately - `waitForShutdown()` now returns `{ debugTraceEvents }` after awaiting shutdown, so trace events are collected only after all compilation events (including persistence spans) have been processed **`index.ts` (parent)**: - Moved `recordTraceEvents(debugTraceEvents)` from the `workerMain` result handler into the `shutdownPromise` `.then()` chain, so events are replayed into the parent reporter after shutdown completes **`to-json-build.ts`**: - Added `turbopack-build-events`, `turbopack-persistence`, `turbopack-compaction` to the allowlist **Test updates**: - Enabled `turbopackFileSystemCacheForBuild: true` in the trace-build test fixture - Updated the Turbopack inline snapshot to include `turbopack-build-events`
192 lines
6.4 KiB
TypeScript
192 lines
6.4 KiB
TypeScript
import { nextTestSetup, isNextDev, isNextStart } from 'e2e-utils'
|
|
import { join } from 'path'
|
|
import { existsSync } from 'fs'
|
|
import { parseTraceFile } from '../../../lib/parse-trace-file'
|
|
|
|
describe('trace-build-file', () => {
|
|
const { next } = nextTestSetup({
|
|
files: __dirname,
|
|
skipStart: !isNextDev,
|
|
skipDeployment: true,
|
|
env: {
|
|
// Enable persistent caching even when the git working directory is
|
|
// dirty (e.g. when developing Next.js itself). Without this, the
|
|
// cache falls back to a temp directory and persistence/compaction
|
|
// spans are not emitted.
|
|
TURBO_ENGINE_IGNORE_DIRTY: '1',
|
|
},
|
|
})
|
|
|
|
if (isNextStart) {
|
|
it('should create .next/trace-build file during production build', async () => {
|
|
// Build the app to trigger trace generation
|
|
await next.build()
|
|
|
|
// Check that trace-build file exists
|
|
const traceBuildPath = join(next.testDir, '.next/trace-build')
|
|
expect(existsSync(traceBuildPath)).toBe(true)
|
|
})
|
|
|
|
it('should contain high-level build trace events', async () => {
|
|
// Ensure we have a fresh build
|
|
await next.build()
|
|
|
|
const traceBuildPath = join(next.testDir, '.next/trace-build')
|
|
expect(existsSync(traceBuildPath)).toBe(true)
|
|
|
|
const traceStructure = parseTraceFile(traceBuildPath)
|
|
|
|
// Should have events
|
|
expect(traceStructure.events.length).toBeGreaterThan(0)
|
|
|
|
// Should contain the main next-build event
|
|
const nextBuildEvents = traceStructure.eventsByName.get('next-build')
|
|
expect(nextBuildEvents).toBeDefined()
|
|
expect(nextBuildEvents.length).toBe(1)
|
|
|
|
const nextBuildEvent = nextBuildEvents[0]
|
|
expect(nextBuildEvent).toHaveProperty('name', 'next-build')
|
|
expect(nextBuildEvent).toHaveProperty('traceId')
|
|
expect(nextBuildEvent).toHaveProperty('id')
|
|
expect(nextBuildEvent).toHaveProperty('duration')
|
|
expect(typeof nextBuildEvent.duration).toBe('number')
|
|
expect(typeof nextBuildEvent.traceId).toBe('string')
|
|
expect(typeof nextBuildEvent.id).toBe('number')
|
|
})
|
|
|
|
it('should only contain allowlisted events', async () => {
|
|
await next.build()
|
|
|
|
const traceBuildPath = join(next.testDir, '.next/trace-build')
|
|
const traceStructure = parseTraceFile(traceBuildPath)
|
|
|
|
// const allowlistedEvents = new Set([
|
|
// 'next-build',
|
|
// 'run-turbopack',
|
|
// 'run-webpack',
|
|
// 'run-typescript',
|
|
// 'run-eslint',
|
|
// 'static-check',
|
|
// 'static-generation',
|
|
// 'output-export-full-static-export',
|
|
// ])
|
|
|
|
const foundEvents = new Set<string>()
|
|
|
|
for (const event of traceStructure.events) {
|
|
foundEvents.add(event.name)
|
|
}
|
|
|
|
if (process.env.IS_TURBOPACK_TEST) {
|
|
expect([...foundEvents].sort()).toMatchInlineSnapshot(`
|
|
[
|
|
"next-build",
|
|
"run-turbopack",
|
|
"run-typescript",
|
|
"static-check",
|
|
"static-generation",
|
|
"telemetry-flush",
|
|
"turbopack-build-events",
|
|
"turbopack-persistence",
|
|
]
|
|
`)
|
|
} else {
|
|
expect([...foundEvents].sort()).toMatchInlineSnapshot(`
|
|
[
|
|
"collect-build-traces",
|
|
"next-build",
|
|
"run-typescript",
|
|
"run-webpack",
|
|
"static-check",
|
|
"static-generation",
|
|
"telemetry-flush",
|
|
]
|
|
`)
|
|
}
|
|
})
|
|
|
|
it('should have next-build as root span with proper hierarchy', async () => {
|
|
await next.build()
|
|
|
|
const traceBuildPath = join(next.testDir, '.next/trace-build')
|
|
const traceStructure = parseTraceFile(traceBuildPath)
|
|
|
|
// Should have no orphaned events (all events should have valid parent references)
|
|
expect(traceStructure.orphanedEvents).toHaveLength(0)
|
|
|
|
// Should have at one root event
|
|
expect(traceStructure.rootEvents.length).toBe(1)
|
|
|
|
// next-build should be the main root event
|
|
const nextBuildEvents = traceStructure.eventsByName.get('next-build')
|
|
expect(nextBuildEvents).toBeDefined()
|
|
expect(nextBuildEvents.length).toBe(1)
|
|
|
|
const nextBuildEvent = nextBuildEvents[0]
|
|
expect(nextBuildEvent.parentId).toBeUndefined() // Should be root
|
|
expect(traceStructure.rootEvents).toContain(nextBuildEvent)
|
|
|
|
// Other build events should be children of next-build or have valid parent references
|
|
const buildEvents = ['run-webpack', 'run-typescript', 'run-eslint']
|
|
for (const eventName of buildEvents) {
|
|
const events = traceStructure.eventsByName.get(eventName)
|
|
if (events && events.length > 0) {
|
|
for (const event of events) {
|
|
if (event.parentId) {
|
|
// Should have a valid parent
|
|
expect(
|
|
traceStructure.eventsById.has(event.parentId.toString())
|
|
).toBe(true)
|
|
const parent = traceStructure.eventsById.get(
|
|
event.parentId.toString()
|
|
)
|
|
|
|
// Parent should either be next-build or another valid event
|
|
expect(parent).toBeDefined()
|
|
expect(parent.traceId).toBe(event.traceId) // Same trace
|
|
}
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
it('should have consistent traceId across all events', async () => {
|
|
await next.build()
|
|
|
|
const traceBuildPath = join(next.testDir, '.next/trace-build')
|
|
const traceStructure = parseTraceFile(traceBuildPath)
|
|
|
|
expect(traceStructure.events.length).toBeGreaterThan(0)
|
|
|
|
const firstEvent = traceStructure.events[0]
|
|
expect(firstEvent.traceId).toBeDefined()
|
|
expect(typeof firstEvent.traceId).toBe('string')
|
|
expect(firstEvent.traceId.length).toBeGreaterThan(0)
|
|
|
|
// All events should have the same traceId
|
|
for (const event of traceStructure.events) {
|
|
expect(event.traceId).toBe(firstEvent.traceId)
|
|
}
|
|
})
|
|
}
|
|
|
|
if (isNextDev) {
|
|
it('should not create trace-build file in development mode', async () => {
|
|
// Make a request to trigger some activity
|
|
await next.render('/')
|
|
|
|
// Check that trace-build file does not exist
|
|
const traceBuildPath = join(next.testDir, '.next/trace-build')
|
|
expect(existsSync(traceBuildPath)).toBe(false)
|
|
})
|
|
}
|
|
|
|
it('should work with basic page rendering', async () => {
|
|
if (isNextStart) {
|
|
await next.start()
|
|
}
|
|
const $ = await next.render$('/')
|
|
expect($('p').text()).toBe('hello world')
|
|
})
|
|
})
|