Endform logo

JavaScript is being rewritten in Rust

OS
Written by Oliver Stenbom

I remember when I first used ESBuild in 2020. At the time I was still writing webpack config files by hand, and the results were… slow.

ESBuild wasn’t the most compatible thing out there, but it was fast. Seeing a 20x performance improvement with ESBuild still made one thing clear: speed has value in itself. Fast tools change how we work, how we iterate, and ultimately how we build software.

ESBuild was written in Go, and it was the start of a speed race, where JS tools weren’t necessarily written in JS anymore. Since then, the JavaScript ecosystem has been quietly migrating to Rust:

  • Biome: Rust-based formatter and linter
  • OXC: A collection of JavaScript tools like a linter and formatter written in Rust
  • Rolldown & Turbopack: Next-generation bundlers aiming to replace Webpack and Rollup
  • Turborepo: Monorepo tooling with intelligent caching
  • Deno: A JavaScript runtime that’s an anagram of Node, also written in Rust

Logos of Rust-based JavaScript tools

What’s particularly interesting is that these aren’t just side projects anymore. Void Zero, a company that encompasses Vite, Rolldown, and OXC, recently raised $4.6 million in seed funding. That is serious, well-funded infrastructure with proper governance and support.

Building with OXC: a real-world example

At Endform, we use OXC extensively for dependency analysis in our CLI. When users write Playwright tests, those files aren’t self-contained - they have dependencies that need to be resolved, potentially transpiled, and packaged for our test runners.

Here’s how we use OXC’s visitor pattern to extract dependencies from JavaScript files:

struct FileDependencyVisitor {
    dependencies: Vec<String>,
}

impl<'a> Visit<'a> for FileDependencyVisitor {
    // Handle ES6 imports: import X from 'Y'
    fn visit_import_declaration(&mut self, it: &oxc_ast::ast::ImportDeclaration<'a>) {
        self.dependencies.push(it.source.value.to_string());
    }

    // Handle CommonJS: require('module') and nested calls
    fn visit_call_expression(&mut self, it: &oxc_ast::ast::CallExpression<'a>) {
        // First, recursively visit nested expressions
        self.visit_expression(&it.callee);
        for arg in it.arguments.iter() {
            if let Some(expr) = arg.as_expression() {
                self.visit_expression(expr);
            }
        }

        // Check if this is a require() call
        if let Some(require) = it.common_js_require() {
            let req_str = require.value.to_string();
            self.dependencies.push(req_str);
        }
    }

    // Handle re-exports: export * from 'module'
    fn visit_export_all_declaration(&mut self, it: &oxc_ast::ast::ExportAllDeclaration<'a>) {
        self.dependencies.push(it.source.value.to_string());
    }
}

The visitor pattern makes it straightforward to traverse JavaScript’s abstract syntax tree and perform custom operations. We use similar approaches for:

  • An environment variable analyzer that detects process.env usage
  • OXC’s built-in parser handles TypeScript to JavaScript transpilation
  • File resolution that maps import paths to actual files in monorepos
  • Finding test cases in user code

Compared to JavaScript-based tools like Vercel’s NFT (Node File Trace), OXC is not only more performant but also had much nicer ergonomics. I have tried using NFT in projects, and it doesn’t feel as stable, or like something I would have wanted to build Endform on top of. It’s really an ecosystem that you feel like you can build on top of.

The TypeScript elephant in the room

While written-in-rust tools seem to be conquering parsing, linting, bundling, and even runtimes, there’s one major piece missing: type checking. TypeScript remains a tricky part of the puzzle, that has the potential to hamper a complete Rust takeover.

People are finding ways around this. Biome v2 just announced support for the first type aware linting rules that don’t require a TypeScript compiler.

Then came the surprise announcement: Microsoft is rewriting TypeScript… in Go.

Microsoft's announcement about TypeScript in Go

Not Rust. Go. This came as a bit of a surprise, especially given Microsoft’s heavy investment in Rust elsewhere. Their reasoning:

  • Structural similarity: Go and TS codebases could be structured similarly - which would make it easier to port
  • Garbage Collection: For TypeScript’s use case, Go’s GC wouldn’t be a significant drawback

I think that this could make it significantly harder for JS to become a language fully written in Rust in the long term. It will also make it harder for Rust-based JS tooling to make cooler type-aware features that use the type system.

More challenges ahead

Extensibility

One major hurdle is community extensibility. Linters are more powerful when developers can write custom rules. But requiring Rust knowledge for something like a simple company-internal custom lint rule creates a significant barrier for the JavaScript community.

Different projects are taking different approaches:

  • Biome: Uses Grit, a query language that compiles to AST operations
  • Rolldown: Exploring shared memory communication between Rust and JavaScript extensions

Dependency sharing

Another challenge is sharing dependencies. At Endform, our CLI compiles OXC crates + our other dependencies into a 15MB binary. If Turborepo also uses OXC tools, or if our users used OXC for linting, it would be great to share those dependencies somehow - but there’s no standard for this today.

Unlike npm’s package.json system, we can’t easily declare and share common Rust dependencies across JavaScript tools.

Rust vs. built-in Rust popularity contest

As a fun little break in this whole analysis - here are a few stats on GitHub stars of some popular Rust and JS projects.

GitHub stars of some popular Rust and JS projects
  • Turborepo is more popular than Axum
  • Deno, a JS runtime written in Rust, is almost as popular as the Rust language itself

Just goes to show how massive the JavaScript ecosystem is and how much impact these Rust tools are having.

The future: total Rust takeover?

I’m not sure that we will see a complete Rust takeover, primarily because TypeScript isn’t going that direction. But we’re definitely seeing Rust become the backbone of JavaScript tooling infrastructure.

The value proposition is clear:

  • Performance: 10-100x improvements aren’t uncommon
  • Stability: These tools are becoming production-grade with proper funding and governance
  • Developer experience: Growing ability to extend / build on top of the tooling

We’re probably at the beginning of this journey, not the end. More companies are leveraging Rust to improve existing ecosystems, and the JavaScript community is one of the biggest beneficiaries.

We are one of those companies here at Endform! We use Rust to make Playwright end-to-end tests faster. Without OXC, we wouldn’t have been able to build the product we have today.

Even if it won’t be a slam dunk, it’s a super interesting space to be watching.

Join the waitlist ✉️ to get notified when we start inviting users.

Frequently Asked Questions

What is Endform?
Endform runs browser based end to end tests for web applications quickly and reliably. We target the end to end testing framework Playwright.
How do I get started with Endform?
Getting started with Endform is easy! Just switch out one CLI command and you are up and running. We are fully Playwright compatible - no configuration changes needed.
How does Endform work?
Endform distributes your Playwright tests across hundreds of machines in the cloud. We run one test per machine, and coordinate the collection of results. This way your test suite finishes in the fastest possible time, while letting you focus on writing tests instead of managing infrastructure.
How fast is Endform compared to other runners?

Endform runs Playwright tests significantly faster than traditional runners by utilizing full parallelization and a highly optimized runtime.

We have seen speedups of some test suites of over 20x, and we can run most test suites in under 2 minutes.

Do you support other test frameworks than Playwright?
No. As of today we only support running Playwright tests. This lets us focus on providing the best possible experience for Playwright users. In the future we may consider adding support for other frameworks.