fix(swc-plugin): preserve imports referenced by step function bodies in client mode#1267
fix(swc-plugin): preserve imports referenced by step function bodies in client mode#1267julianbenegas wants to merge 9 commits into
Conversation
🦋 Changeset detectedLatest commit: 8fd78e0 The changes in this PR will be included in the next version bump. This PR includes changesets to release 16 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
📊 Benchmark Results
workflow with no steps💻 Local Development
▲ Production (Vercel)
workflow with 1 step💻 Local Development
▲ Production (Vercel)
workflow with 10 sequential steps💻 Local Development
▲ Production (Vercel)
workflow with 25 sequential steps💻 Local Development
▲ Production (Vercel)
workflow with 50 sequential steps💻 Local Development
▲ Production (Vercel)
Promise.all with 10 concurrent steps💻 Local Development
▲ Production (Vercel)
Promise.all with 25 concurrent steps💻 Local Development
▲ Production (Vercel)
Promise.all with 50 concurrent steps💻 Local Development
▲ Production (Vercel)
Promise.race with 10 concurrent steps💻 Local Development
▲ Production (Vercel)
Promise.race with 25 concurrent steps💻 Local Development
▲ Production (Vercel)
Promise.race with 50 concurrent steps💻 Local Development
▲ Production (Vercel)
Stream Benchmarks (includes TTFB metrics)workflow with stream💻 Local Development
▲ Production (Vercel)
SummaryFastest Framework by WorldWinner determined by most benchmark wins
Fastest World by FrameworkWinner determined by most benchmark wins
Column Definitions
Worlds:
❌ Some benchmark jobs failed:
Check the workflow run for details.
Check the workflow run for details. |
🧪 E2E Test Results❌ Some tests failed Summary
❌ Failed Tests▲ Vercel Production (54 failed)astro (4 failed):
example (9 failed):
express (7 failed):
fastify (6 failed):
hono (6 failed):
nitro (7 failed):
sveltekit (8 failed):
vite (7 failed):
🌍 Community Worlds (54 failed)turso (54 failed):
📋 Other (3 failed)e2e-local-dev-nest-stable (1 failed):
e2e-local-postgres-nest-stable (1 failed):
e2e-local-prod-nest-stable (1 failed):
Details by Category❌ ▲ Vercel Production
✅ 💻 Local Development
✅ 📦 Local Production
✅ 🐘 Local Postgres
❌ 🌍 Community Worlds
❌ 📋 Other
❌ Some E2E test jobs failed:
Check the workflow run for details. |
…in client mode The SWC plugin's dead code elimination unconditionally skipped step function bodies during usage analysis in all modes. This is correct for workflow mode (where step bodies are replaced with stubs) but wrong for client mode (where step bodies are kept intact). Imports referenced only inside step function bodies were considered "unused" and stripped, causing ReferenceError when step functions were called directly from server-side code (e.g. route handlers). The fix adds a mode-aware `skip_step_bodies` flag to the usage collector, set to true only in workflow mode. Made-with: Cursor
0d27157 to
50a9096
Compare
The stepDirectCallWithImports e2e test needs this endpoint in every framework, not just Next.js. Adds the route and symlinks the workflow files (_direct_call_step.ts, _direct_call_helper.ts) into nitro-v3 and sveltekit (which feed the other framework workbenches via directory symlinks). Made-with: Cursor
The client-mode transform now correctly preserves imports referenced by step function bodies. This means the './serde-models.js' import survives into the Turbopack build, which can't resolve .js extensions through symlinks. Drop the extension so Turbopack resolves it properly. Made-with: Cursor
pranaygp
left a comment
There was a problem hiding this comment.
Good fix. The root cause is well-understood: ComprehensiveUsageCollector was unconditionally skipping step function bodies during usage analysis, but in client mode those bodies are preserved intact — so their imports are needed at runtime.
The fix is clean and targeted:
skip_step_bodiesflag correctly scoped to workflow mode only- Hoisted step function bodies (object property + nested) are analyzed before they're inserted into the module, avoiding the timing gap with dead code elimination
- stepId assignments for hoisted functions are now inserted inline (right after the declaration) instead of deferred to
registration_calls— this is a nice improvement for readability and correctness of ordering
All the fixture output changes are consistent with the new behavior. Workflow-mode outputs are unaffected (step-only imports still correctly stripped). Test coverage is thorough: 2 new fixtures, updated expectations for 6 existing fixtures, and an e2e test propagated across all workbenches.
|
@TooTallNate The e2e failures on CI (nextjs-turbopack, nextjs-webpack, nuxt, nest) are caused by this fix being too broad in what it preserves in client mode. What's happening: The // Step function that spawns another workflow using start()
async function spawnChildWorkflow(value: number) {
'use step';
const childRun = await start(childWorkflow, [value]);
return childRun.runId;
}
async function awaitWorkflowResult<T>(runId: string) {
'use step';
const run = getRun<T>(runId);
// ...
}Previously, Non-Next.js workbenches (express, fastify, hono, etc.) pass because they're server-only and don't bundle for the browser. The tension: The fix correctly preserves step-body imports for the direct-call case (route handler calling a step function). But it also preserves server-only imports like The fix likely needs to be more targeted — e.g., always stripping known server-only modules ( |
|
Superseded by #1312. |
Summary
"use step"function bodies. Previously, the usage collector unconditionally skipped step function bodies in all modes, treating them as replaced — but in client mode, step bodies are kept intact and may be called directly from server-side code (e.g. route handlers). This causedReferenceErrorat runtime for any import only referenced inside a step body.skip_step_bodiesflag to theComprehensiveUsageCollector, set totrueonly in workflow mode (where step bodies are actually replaced with stubs).Test plan
stepDirectCallWithImportspasses against nextjs-turbopack dev serverMade with Cursor