How does withWorkflow handle webpack and turbopack?

HEAD
c1d7c8dAdd support for `"use step"` functions in class instance methods (#777)
This post might have stale content, as HEAD is 46 commits ahead.

How does the Next.js plugin (withWorkflow) handle both webpack and turbopack? What's the integration pattern for both bundlers?

Avatar of anthropic/claude-opus-4.5
Claude Opus 4.5
commented

This gives me a comprehensive understanding. Let me summarize the integration pattern:


##How withWorkflow Handles Webpack and Turbopack

The Next.js plugin (withWorkflow) in packages/next/src/index.ts handles both bundlers through a unified loader approach with a single shared SWC transformation. Here's the integration pattern:

###1. Unified Loader Architecture

Both webpack and turbopack use the same loader (packages/next/src/loader.ts), which:

  • Uses @swc/core to transform source files
  • Applies the @workflow/swc-plugin SWC plugin
  • Transforms files containing "use workflow", "use step" directives, or custom serialization patterns

###2. Turbopack Configuration (Lines 67-109)

// configure the loader if turbopack is being used
if (!nextConfig.turbopack) {
  nextConfig.turbopack = {};
}
if (!nextConfig.turbopack.rules) {
  nextConfig.turbopack.rules = {};
}

// For each file extension:
for (const key of ['*.tsx', '*.ts', '*.jsx', '*.js', '*.mjs', '*.mts', '*.cjs', '*.cts']) {
  nextConfig.turbopack.rules[key] = {
    // Next.js 16+ supports content-based conditions for efficiency
    ...(supportsTurboCondition ? {
      condition: {
        all: [
          // Exclude generated workflow route files
          { not: { path: /[/\\]\.well-known[/\\]workflow[/\\]/ } },
          // Only process files with workflow directives or serde patterns
          { content: /(use workflow|use step|from\s+(['"])@workflow\/serde\2|Symbol\.for...)/ },
        ],
      },
    } : {}),
    loaders: [...existingLoaders, loaderPath],
  };
}

Key Turbopack features:

  • Uses turbopack.rules configuration with file extension patterns
  • In Next.js 16+, uses content-based conditions (condition.content) to only transform relevant files
  • Path exclusion (condition.not.path) to skip generated workflow files
  • Preserves any existing user-defined rules/loaders

###3. Webpack Configuration (Lines 112-131)

// configure the loader for webpack
const existingWebpackModify = nextConfig.webpack;
nextConfig.webpack = (...args) => {
  const [webpackConfig] = args;
  
  // Add loader rule at the end (webpack applies bottom-to-top)
  webpackConfig.module.rules.push({
    test: /.*\.(mjs|cjs|cts|ts|tsx|js|jsx)$/,
    loader: loaderPath,
  });

  // Chain with user's existing webpack config
  return existingWebpackModify ? existingWebpackModify(...args) : webpackConfig;
};

Key Webpack features:

  • Uses the webpack config function hook pattern
  • Adds loader rule via module.rules.push() (runs bottom-to-top, so runs before built-in SWC)
  • Chains with existing webpack config if user has one
  • Unlike turbopack, doesn't use content matching at the bundler level - the loader itself handles pattern detection

###4. The Shared Loader (loader.ts)

Both bundlers use the same loader which:

  1. Pattern Detection: Uses detectWorkflowPatterns() to check for:

    • "use workflow" / "use step" directives
    • @workflow/serde imports
    • Symbol.for('workflow-serialize') patterns
  2. Skip Logic: Returns early (no transform) for:

    • Generated workflow files (.well-known/workflow/)
    • SDK internal files without directives
  3. SWC Transform: Applies the SWC plugin in 'client' mode which:

    • Generates workflow/step IDs
    • Replaces function bodies with stubs that call the runtime
    • Emits JSON manifests for discovery

###5. Build-Time Processing

In addition to the loader transform, withWorkflow also runs a NextBuilder (lines 133-163) that:

  • Scans pages, app, src/pages, src/app directories
  • Bundles workflow and step functions for server-side execution
  • Generates route handlers at .well-known/workflow/v1/
  • Creates the unified manifest

###Key Differences Summary

AspectTurbopackWebpack
Config LocationnextConfig.turbopack.rulesnextConfig.webpack()
File MatchingExtension-based rules (*.ts)Regex test (`/.(ts
Content FilteringAt bundler level (v16+)Inside loader
Rule AdditionMerge with existing rulesPush to module.rules

The beauty of this design is that both bundlers use the identical loader and SWC plugin, ensuring consistent transformation regardless of which bundler Next.js uses.


END OF POST

How does withWorkflow handle webpack and turbopack? — vercel/workflow