oven-sh / bun

Incredibly fast JavaScript runtime, bundler, test runner, and package manager – all in one
https://bun.sh
Other
73.52k stars 2.71k forks source link

Emit JavaScriptCore bytecode directly from Bun's AST #6798

Open Jarred-Sumner opened 11 months ago

Jarred-Sumner commented 11 months ago

Executables like tsc, prettier and more start too slowly in Bun. This happens because Bun transpiles every file, even when it doesn't need to -- and then JavaScriptCore parses the transpiled output (again).

To address the performance problems from transpiling every single file in Bun's runtime, we have to stop parsing every file twice.

There are roughly two general approaches:

1) Don't parse/transpile in Bun when possible. Find ways to avoid it through convention or configuration. This is the simplest approach and the easiest to maintain, but it will make the DX worse. 2) Don't parse in JavaScriptCore when possible.

Let's explore what it would mean to take the second approach. It's the "crazy" one. The one that most would (rationally) say "that sounds like way too much work."

These seem like the two relevant files in JavaScriptCore:

https://github.com/oven-sh/WebKit/blob/b82708ff8c18785f0f8c210f27cef316c7e40fda/Source/JavaScriptCore/parser/Nodes.h

https://github.com/oven-sh/WebKit/blob/b82708ff8c18785f0f8c210f27cef316c7e40fda/Source/JavaScriptCore/bytecompiler/NodesCodegen.cpp

There are multiple ways to go about implementing this.

I think it would likely be unmaintainable to implement an alternative of NodesCodgen that uses Bun's AST. This file is frequently changed and it likely needs to be maintained side-by-side with the interpreter and JIT tiers. It would make updating JavaScriptCore difficult.

But, we could lazily convert from Bun's AST nodes into JavaScriptCore's. Instead of a js_printer, what if we also had an ast_printer? I think this is an approach worth experimenting with.

Some unanswered questions:

aralroca commented 11 months ago

Maybe it is related... naturally if we can avoid parsing better, but many cases Bun plugins also need to parse, in the end it makes that each Bun plugin can have a different parser, making that:

It would be nice to pass some of this responsibility to the plugins, offering something like this:

const { parse, generate, traverse } = await Bun.ast('tsx')
const ast = parse('const hello = "world"')

ast.body.declarations.init.value = "bun"

console.log(generate(ast)) // const hello = "bun"
MulverineX commented 4 months ago

This would also be very useful for external projects wanting to pre-compile Bun javascript code for distribution & execution outside of the npm ecosystem. (think embedded applications like modding APIs)