Achieving 87x Faster Server-Side Action Builds: Migrating from Rollup to Rolldown

Introduction

I'm @kazupon from the Developer Experience team.

PLAID offers a product called KARTE that enables continuous improvement by providing popup dialogs to users visiting their website through campaigns, analyzing visualized user behavior, and implementing measures. We provide an editor called "Flex Editor" for editing these campaigns.

We've achieved a massive performance improvement in KARTE's no-code editor Flex Editor by accelerating action build times up to 87x, dramatically enhancing the user experience. In this post, I'll walk you through our journey of migrating from Rollup to Rolldown for server-side build optimization.

This article is a follow-up to our previous post:

How We Made Rollup Build Times 10x Faster to Improve Preview Display

Actions are applications that allow editing action templates (internally called "campaign templates") through KARTE's admin panel. They're edited using "Flex Editor", a no-code editor that builds JavaScript code server-side for execution as marketing campaigns on websites.

The Challenge: Build Times Hurting UX

When users save edited actions in Flex Editor, they're built server-side for web delivery. However, lengthy build times were causing slow server responses, forcing users to wait and degrading the overall experience.

According to our DataDog metrics, action-compiler build times ranged from 5-10 seconds, sometimes exceeding 20 seconds.

DataDog metrics showing server-side build times ranging from 5-10 seconds

The primary culprit? JavaScript-based bundlers running server-side.

As mentioned in our previous article, action-compiler internally uses Rollup, a JavaScript-based module bundler. Module bundling is inherently CPU-intensive. While modern JavaScript engines like V8 and SpiderMonkey can achieve near-native performance through internal optimizations, they're not ideal for CPU-heavy tasks.

Bundlers like Rollup, which parse massive amounts of JavaScript code and perform tree-shaking on module graphs, suffer from JavaScript's event-loop-based single-threaded architecture when it comes to CPU-intensive operations.

While Web Workers or Node.js worker threads (node:worker_thread) can provide parallelization, Rollup's architecture wasn't designed with parallel bundling optimization in mind.

Additionally, to maximize action performance on websites, we use Terser (a code minifier) for minification. Since minification involves static code analysis followed by compression, it adds even more CPU overhead on top of Rollup's bundling process.

The Solution: Migrating to Rolldown

To tackle server-side action build times, we decided to migrate from Rollup to Rolldown.

Rolldown is a high-performance JavaScript bundler written in Rust with Rollup API compatibility, promising significant bundling performance improvements.

Rolldown addresses Rollup's performance challenges through several innovations. Bundler operations like JavaScript module parsing and module graph tree-shaking are accelerated using crates from the Oxc project and Rust-side multi-threaded parallelization.

While maintaining API compatibility for existing Rollup plugins, Rolldown's architecture enables plugins to run in parallel on the JavaScript side through Workers, coordinating with the Rust side.

At the time of writing, Rolldown is at v1.0.0 beta.32. When we evaluated migration (January 2025), we found:

These factors and verification results led us to adopt Rolldown for the Flex Editor project.

Migration Approach

Thanks to Rolldown's Rollup API compatibility, migrating my OSS projects and packages within Flex Editor was straightforward. However, action-compiler presented unique challenges.

As explained in our previous article, we have two types of action-compiler:

  • For Browser: Used for action preview functionality in Flex Editor's GUI application
  • For Server (Node.js): Used server-side to build actions for deployment as campaign applications on websites

Architecture diagram showing action-compiler for both browser and server environments

Both environments use Rollup to build actions into JavaScript code. It's a single-source package optimized for performance in each environment.

The browser-oriented action-compiler pre-bundles JavaScript modules that actions depend on to improve editor preview UX, as mentioned in our previous article. It even bundles Rollup itself into action-compiler for optimized build speed.

The server-oriented action-compiler bundles with Rollup and minifies with Terser (via Rollup plugin) to maximize action performance. Like the browser version, action-compiler itself is bundled, but unlike the browser version, it simply imports Rollup from node_modules rather than bundling Rollup into action-compiler.

While each environment uses Rollup appropriately with environment-specific configurations, maintaining a single source while using Rolldown only server-side required careful planning. We wanted to avoid code complexity and increased maintenance cognitive load from implementing Rolldown-specific code.

Leveraging API Compatibility for Rolldown Migration

We achieved server-side action-compiler Rolldown migration by leveraging API compatibility and using a bundler to rewrite Rollup imports in server-side implementation code.

Specifically, we implemented a Rollup plugin that replaces import { rollup } from 'rollup' with import { rolldown as rollup } from 'rolldown'.

This gets a bit complex, but action-compiler is a JavaScript package that compiles (bundles) actions into JavaScript code using Rollup. As mentioned, we bundle action-compiler itself for browser and server optimization. (In other words, we're bundling a bundler with a bundler!)

Here's the relevant excerpt from action-compiler's bundler configuration file (rolldown.config.js) - note that action-compiler's bundler is now Rolldown:

// ...

/**
 * Replace import syntax with rolldown when using in Node.js environment
 */
if (isNode && useRolldown) {
  plugins.push({
    name: 'replace-import-syntax',
    transform(code, id) {
      if (id.endsWith('src/prod.ts') || id.endsWith('src/dev.ts')) {
        return code.replace(/import\s+{\s+rollup\s+}\s+from\s+['"]rollup['"]\s*;?/gm, () => {
          return `import { rolldown as rollup } from 'rolldown';`
        })
      }
    }
  })
}

// ...

That's it!
(The implementation uses simple regex string replacement rather than AST-based static analysis since we're only transforming two files.)

isNode determines if the bundle target is Node.js - true for Node.js (server-side), false for browser. useRolldown determines whether to use Rolldown server-side - true uses Rolldown, false continues using Rollup.

When both isNode and useRolldown are true, the bundler registers a plugin that rewrites import { rollup } from 'rollup' to import { rolldown as rollup } from 'rolldown' in action-compiler's code.

By setting these flags appropriately (both true) when bundling action-compiler for server use, the code is transformed to use Rolldown. Using the bundler eliminated the need for Rolldown-specific code in action-compiler itself.

Rolldown Server-Side Build Performance

With Rollup successfully replaced by Rolldown in server-side action-compiler, we measured build performance using the following configurations and environment.

Metrics

  1. Rollup[1] (no minification)
  2. Rolldown[2] (no minification)
  3. Rollup + Terser (@rollup/plugin-terser[3])
  4. Rolldown + SWC (rollup-plugin-swc3[4])
  5. Rolldown + built-in minifier (oxc-minify[5])

For comparison, we included cases without minification and the Rollup + Terser combination. Since oxc-minify was in WIP status during our implementation and might not function correctly for actions in the Flex Editor beta production environment, we also measured performance with Terser and SWC available as Rollup plugins.

Environment

Performance measurements were conducted on:

  • Machine specs:
    • Model: MacBook Pro 14-inch, 2021
    • Chipset: Apple M1 Max
    • Memory: 64GB

The action structure for build performance testing:

  • Build target action structure:
    • index.tsx
    • App.svelte
    • gen.ts
    • customScript.ts
    • karte-template.json
    • props.json
    • variablesQuery.json
    • localVariablesQuery.json

The action UI represents a campaign like this:

Example campaign UI created with the action in Flex Editor

While we'll skip the detailed file contents, when editing and saving in the editor, action file code is serialized into JSON with this structure and sent server-side for building:

JSON structure showing serialized action file codes sent to server for building

For these performance measurements, we used server-oriented action-compiler to build this action code.

Build Performance Results

Here are our performance measurement results:

Build Times

Configuration Max Build Time Min Build Time Average Build Time
Rollup (no minification) 560.596119 ms 205.13774 ms 236.0695421 ms
Rolldown (no minification) 112.801283 ms 23.01124 ms 33.42461761 ms
Rollup + Terser (@rollup/plugin-terser) 2424.394387 ms 959.403731 ms 1149.732976 ms
Rolldown + SWC (rollup-plugin-swc3) 235.093658 ms 106.845099 ms 125.2551525 ms
Rolldown + built-in (oxc-minify) 143.829239 ms 28.230994 ms 40.44968284 ms

Without minification, Rolldown clearly outperforms Rollup. Comparing Rolldown's minimum build time to Rollup's maximum shows approximately 24x improvement, with an average 9x performance boost. These results confirm Rolldown's acceleration benefits for action builds.

With minification, Rolldown builds still outperform Rollup + Terser. Comparing Rolldown + SWC's minimum build time to Rollup + Terser's maximum shows about 23x improvement, averaging 9x faster. With Rolldown's built-in minifier, we achieved an incredible 87x improvement at minimum and 29x on average.

These results demonstrate that while the SWC minifier via rollup-plugin-swc3 provides acceleration, using Rolldown's built-in oxc-minifier delivers overwhelming build performance gains.[6]

File Sizes

Configuration Minified Size Gzipped Size
Rollup + Terser 92.15 kb 31.38 kb
Rolldown + SWC 114.73 kb 39.11 kb
Rolldown + built-in minifier 122.09 kb 40.35 kb

File size results revealed interesting insights for action builds.

Various Rolldown minification configurations resulted in approximately 20-25% larger bundle sizes compared to Rollup + Terser. This is because Svelte runtime code used by actions is pre-bundled with Rolldown during action-compiler bundling.

Rolldown's built-in minifier oxc-minify has several open issues regarding minification optimization. Our analysis of the built code confirmed issues matching these reported problems.

The reason for pre-bundling Svelte runtime code is to accelerate Flex Editor preview display, as explained in our previous article.

Comparing minification performance between SWC (rollup-plugin-swc3) and oxc-minify, SWC showed higher compression rates.

Trade-offs with Rolldown Adoption

Performance measurements of Rolldown-powered server-side action-compiler showed build times exceeded expectations, while bundled action JavaScript file sizes increased 20-27% compared to Rollup + Terser.

Our goal was to accelerate server-side action build times and improve the save experience in Flex Editor. After team discussion, we decided to adopt Rolldown with its built-in minifier (oxc-minifier) for server-side build acceleration.

The trade-off: JavaScript action files delivered will be larger than Rollup-bundled versions. However, new actions from Flex Editor have been reduced to 1/10th the size of current production actions through architecture renewal. Server-side build acceleration with Rolldown not only improves DX through reduced server infrastructure costs but also enhances end-user UX when saving in Flex Editor. The dual benefits of DX and UX improvements drove our decision to adopt Rolldown with built-in oxc-minifier.

Production Build Performance

At the time of writing, we've deployed Rolldown-powered server-side action-compiler to production as a beta for select customers using Flex Editor. We continuously monitor server-side action-compiler performance with DataDog. Here's the graph after introducing Rolldown-powered action-compiler to production:

DataDog graph showing dramatic reduction in build times after Rolldown deployment

In production, the system runs in a Kubernetes (k8s) container environment. Pre-migration action-compiler build times of 5-10 seconds have dramatically reduced to hundreds of milliseconds. The accelerated server-side build times when editing and saving actions in Flex Editor result in faster server responses and noticeably improved editor UX.

Future Outlook

Towards Rolldown's Official Release

While Flex Editor currently uses Rolldown beta, we'll continue updating toward the official release. We hope the file size issues mentioned earlier will be resolved by Rolldown's official release. Vite is also progressing with Rolldown integration through rolldown-vite, and we've verified up to 16x acceleration in the Flex Editor project.

Technical Future Prospects

With Rolldown adoption, we've dramatically improved build time challenges. Our next step involves exploring AST-based editor generalization. While currently using Svelte-based AST, we're considering migration to Vapor's AST/IR base using inclusion-vapor and expansion to other products.

Conclusion

This article detailed our journey migrating from Rollup to Rolldown, dramatically improving server-side build times and enhancing product UX.

Despite being in beta, Rolldown's core functionality is nearly complete and ready for production use. JavaScript/TypeScript projects can especially benefit from significant build time reductions, improving DX and accelerating CI/CD deployment cycles.

I hope this article inspires you to consider Rolldown for your own projects.

If you're interested in working on projects while contributing to OSS, please check out our careers page.


  1. v4.35.0 ↩︎

  2. v1.0.0-beta.32 ↩︎

  3. v0.4.4 ↩︎

  4. v0.12.1, using minify feature ↩︎

  5. Minifier provided by the oxc project ↩︎

  6. This performance measurement compares only SWC and Rolldown's minifier under Rolldown. As shown in various minification benchmarks, performance varies depending on the code being minified. ↩︎