refactor(next): remove step file copy mechanism from deferred builder#1796
refactor(next): remove step file copy mechanism from deferred builder#1796TooTallNate merged 6 commits intomainfrom
Conversation
Step sources are now imported directly into the generated `step/route.js` using the same `getImportPath`-based logic already used for serde files. This is possible because the SWC plugin's client mode was merged into step mode (#1686), so the workflow loader always runs in step mode and transforms every file it sees, including those from packages. Removed: - `__workflow_step_files__/` per-file copies with hashed names, metadata comments, inline source maps, and bare-specifier rewriting via `enhanced-resolve` - `createResponseBuiltinsStepFile`; builtins are now imported via `require.resolve('workflow/internal/builtins')` at build time - `step-copy-utils.ts` and all copy-specific branches in `loader.ts` - `enhanced-resolve` dependency The deferred builder still removes the legacy `__workflow_step_files__/` directory on boot so upgrades leave no stale artifacts behind. Dev tests that inspected copied step file contents now inspect the manifest.json entries (which list every discovered step keyed by source path).
🦋 Changeset detectedLatest commit: c023fef The changes in this PR will be included in the next version bump. This PR includes changesets to release 17 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
workflow with 1 step💻 Local Development
workflow with 10 sequential steps💻 Local Development
workflow with 25 sequential steps💻 Local Development
workflow with 50 sequential steps💻 Local Development
Promise.all with 10 concurrent steps💻 Local Development
Promise.all with 25 concurrent steps💻 Local Development
Promise.all with 50 concurrent steps💻 Local Development
Promise.race with 10 concurrent steps💻 Local Development
Promise.race with 25 concurrent steps💻 Local Development
Promise.race with 50 concurrent steps💻 Local Development
workflow with 10 sequential data payload steps (10KB)💻 Local Development
workflow with 25 sequential data payload steps (10KB)💻 Local Development
workflow with 50 sequential data payload steps (10KB)💻 Local Development
workflow with 10 concurrent data payload steps (10KB)💻 Local Development
workflow with 25 concurrent data payload steps (10KB)💻 Local Development
workflow with 50 concurrent data payload steps (10KB)💻 Local Development
Stream Benchmarks (includes TTFB metrics)workflow with stream💻 Local Development
stream pipeline with 5 transform steps (1MB)💻 Local Development
10 parallel streams (1MB each)💻 Local Development
fan-out fan-in 10 streams (1MB each)💻 Local Development
SummaryFastest Framework by WorldWinner determined by most benchmark wins
Fastest World by FrameworkWinner determined by most benchmark wins
Column Definitions
Worlds:
|
🧪 E2E Test Results✅ All tests passed Summary
Details by Category✅ ▲ Vercel Production
✅ 💻 Local Development
✅ 📦 Local Production
✅ 🐘 Local Postgres
✅ 🪟 Windows
✅ 📋 Other
|
There was a problem hiding this comment.
Pull request overview
Refactors the Next.js deferred builder to stop copying step source files into a generated directory, instead importing step/serde sources directly from their original locations (mirroring existing serde handling) and simplifying the loader accordingly.
Changes:
- Remove deferred step copy utilities and copy/rewrite logic; generate
step/route.jswith direct imports (including workflow response builtins). - Simplify the Next loader by removing step-copy-file special casing and always using the actual filename/module specifier inputs.
- Update dev E2E assertions to validate manifest/route imports (rather than copied file contents) and drop the unused dependency.
Reviewed changes
Copilot reviewed 6 out of 7 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
packages/next/src/builder-deferred.ts |
Removes step-copy pipeline and switches step route generation to direct imports + legacy cleanup. |
packages/next/src/loader.ts |
Deletes deferred step-copy branches and unifies transform/discovery behavior. |
packages/next/src/step-copy-utils.ts |
Deleted (no longer needed). |
packages/next/package.json |
Drops enhanced-resolve dependency. |
pnpm-lock.yaml |
Removes enhanced-resolve from @workflow/next importer. |
packages/core/e2e/dev.test.ts |
Updates dev E2E checks to assert via manifest + route import specifiers. |
.changeset/remove-step-file-copy.md |
Adds release note for the behavioral change. |
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| } | null { | ||
| let resolved: string; | ||
| try { | ||
| resolved = require.resolve('workflow/internal/builtins', { | ||
| paths: [this.config.workingDir], | ||
| }); | ||
| } catch { | ||
| return null; |
There was a problem hiding this comment.
resolveResponseBuiltinsStepSource() silently returns null when workflow/internal/builtins cannot be resolved. If the app has an incompatible workflow version (or resolution differs under pnpm), this will build a step route without registering the builtin response steps, likely causing runtime failures when Request/Response builtins are invoked. Consider failing fast with a clear error (e.g., instruct to upgrade workflow) or at least logging a warning and keeping the previous in-repo fallback behavior so builtins are always present.
| } | null { | |
| let resolved: string; | |
| try { | |
| resolved = require.resolve('workflow/internal/builtins', { | |
| paths: [this.config.workingDir], | |
| }); | |
| } catch { | |
| return null; | |
| } { | |
| let resolved: string; | |
| try { | |
| resolved = require.resolve('workflow/internal/builtins', { | |
| paths: [this.config.workingDir], | |
| }); | |
| } catch (error) { | |
| const message = | |
| error instanceof Error ? ` ${error.message}` : ''; | |
| throw new Error( | |
| `Unable to resolve "workflow/internal/builtins" from working directory ` + | |
| `"${this.config.workingDir}". Builtin Request/Response workflow steps ` + | |
| `require a compatible "workflow" package to be installed and resolvable ` + | |
| `from the application. Please ensure the app depends on a compatible ` + | |
| `"workflow" version and upgrade it if necessary.${message}` | |
| ); |
Summary
Follow-up to #1686 (removing client transform mode). Step sources in the Next.js deferred builder are now imported directly into the generated
step/route.jsinstead of being copied into__workflow_step_files__/per-file with hashed names, metadata comments, and rewritten imports. This mirrors how serde files are already handled in the same builder.Why this is now possible
The step file copy mechanism was introduced in dc2dc6a with two stated reasons:
relativeFilenamefor deterministic step IDs — the copy carried metadata so the loader fed SWC the original relative filename.Both concerns are now moot:
getRelativeFilenameForSwc(includingresolveWorkflowAliasRelativePath) already produces stable IDs from the original path.Changes
packages/next/src/builder-deferred.ts:copyDiscoveredStepFiles,createResponseBuiltinsStepFile,rewriteRelativeImportsForCopiedStep,rewriteCopiedStepImportSpecifier,resolveBareCopiedStepSpecifier,getStepCopyFileName.buildStepsFunctionso the step route imports originals viagetImportPath(package specifier for workspace/node_modules, relative path for app files).require.resolve('workflow/internal/builtins')at build time and emit one import.__workflow_step_files__/directory rm on boot so upgrades leave no stale artifacts.packages/next/src/loader.ts:isDeferredStepCopyFilePathbranches: metadata parsing, inline-sourcemap chaining, skip-transform bypass,discoveryFilePathindirection,isGeneratedWorkflowFilecarve-out.filenamefor the SWC relative filename and module specifier.packages/next/src/step-copy-utils.ts: deleted.packages/next/package.json: dropenhanced-resolvedependency.packages/core/e2e/dev.test.ts: renamesupportsDeferredStepCopies→usesDeferredBuilder; assert step registration viamanifest.jsonentries and import specifiers in the generated route, not copied file contents.Diff stats
Verification
Local verification against
workbench/nextjs-turbopack:pnpm build(all packages): passpnpm typecheck: passpnpm -C workbench/nextjs-turbopack build: pass; generatedstep/route.jscontains direct imports (incl.workflow/internal/builtins,@workflow/ai/agent, workspace and app files);__workflow_step_files__/directory not created; manifest has 27 steps / 16 workflows / 3 classes.packages/core/e2e/e2e.test.tsagainsthttp://localhost:3000): 71/71 pass.packages/core/e2e/dev.test.ts): 6/6 pass against a fresh dev server.