Skip to content

feat: embed SWC TypeScript transpiler and load .ts modules directly#226

Open
benpalevsky wants to merge 1 commit into
godotjs:mainfrom
hatgg:5-swc-transpiler
Open

feat: embed SWC TypeScript transpiler and load .ts modules directly#226
benpalevsky wants to merge 1 commit into
godotjs:mainfrom
hatgg:5-swc-transpiler

Conversation

@benpalevsky

@benpalevsky benpalevsky commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

Part 1 of 3 — the SWC stack. Foundation; PRs 227 and 228 build on this one.

Problem

Today, a .ts file has to be compiled to .js by tsc before GodotJS can run it. That means a separate build step and a dependency on having TypeScript installed.

What this adds

An embedded TypeScript transpiler, so the engine can load .ts modules directly. A small Rust library (SWC, a fast TypeScript-to-JavaScript compiler) is built into the engine and called through a tiny interface — two C functions and one struct. When a .ts module loads, it's transpiled in-process and run, with no separate tsc step.

It is opt-in and off by default: the transpiler only builds with scons use_typescript_transpiler=yes. With the flag off (the default), no Rust toolchain is needed and the build is unchanged from today.

The pieces:

  • transpiler-ffi/ — the Rust staticlib and its C header (the two-function/one-struct boundary).
  • SCsub / config.py — the use_typescript_transpiler build flag (default off), and building/linking the staticlib when it's on.
  • bridge/jsb_module_resolver.cpp — transpile .ts on load, and probe for .ts during resolution. Both are behind #if JSB_WITH_TYPESCRIPT_TRANSPILER, so with the flag off the resolver behaves exactly as today (no .ts probe, no transpile path).
  • weaver/jsb_script.cpp — with the flag on, attach a .ts script by loading the .ts source directly (transpiled in-process); with the flag off it keeps loading the tsc-emitted .js via convert_typescript_path, as before this change.

What it costs (please weigh this)

With the flag on, building the engine needs a Rust toolchain (cargo on PATH), and the first build downloads and compiles the SWC dependency tree (pinned in Cargo.lock). With the flag off — the default — there is no new prerequisite: no Rust, no cargo build, and the engine builds exactly as it does today.

Robustness

The Rust FFI is panic-safe: a panic inside the transpiler (for example SWC choking on malformed input) is caught at the C boundary and returned as an error result, never unwinding into the engine.

How to verify

  • Default build (flag off): build on any platform without Rust installed — it should build exactly as before this PR.
  • use_typescript_transpiler=yes: build on Linux, Windows, and macOS — the static-link fix (allowing duplicate rust_eh_personality symbols against Godot's prebuilt static libs) specifically needs cross-platform confirmation. Then load a .ts module and confirm it runs without a separate tsc step.

A changeset is included.

Compiles a Rust staticlib (transpiler-ffi/) built around SWC and exposed through a minimal two-function/one-struct C FFI, wired into the engine via SCsub. jsb_module_resolver loads .ts sources directly by transpiling them in-process into the CommonJS module wrapper, so TypeScript no longer needs a separate tsc emit step to run.

Opt-in via a new use_typescript_transpiler build flag, off by default. With the flag off (the default) no Rust toolchain enters the build graph and every .ts code path is compiled out behind JSB_WITH_TYPESCRIPT_TRANSPILER: script loading keeps using the tsc-emitted .js (convert_typescript_path), the resolver does not probe .ts, and the build behaves as before this change.

The FFI is panic-safe (catch_unwind at the boundary) and hands buffers to C through Box<[u8]> so the C-side free is sound. Build flags in SCsub allow the staticlib's libstd symbols to coexist with Godot's prebuilt accesskit staticlib on Linux/Windows.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant