Skip to content

feat: pre-transpile .ts on export and resolve to it at runtime (no-transpiler targets)#228

Open
benpalevsky wants to merge 3 commits into
godotjs:mainfrom
hatgg:7-swc-editor
Open

feat: pre-transpile .ts on export and resolve to it at runtime (no-transpiler targets)#228
benpalevsky wants to merge 3 commits into
godotjs:mainfrom
hatgg:7-swc-editor

Conversation

@benpalevsky

Copy link
Copy Markdown
Contributor

Part 3 of 3 — the SWC stack. Builds on PR 6 (6-swc-sourcemaps).

Problem

Two friction points around tsc:

  1. Exporting a project needed tsc to turn .ts into .js first.
  2. The editor's Start button hard-blocked until tsc was installed.

What this adds

  • Export-time pre-transpile. The export plugin runs .ts through SWC when you export, so exported builds contain ready-to-run JavaScript and don't need tsc. Pre-transpile is the default export behavior; it's governed by the .../editor/packaging/include_typescript_source setting, which defaults to off — with it off, each .ts is pre-transpiled to .js at export; turning it on instead packs the raw .ts so the runtime SWC transpiler runs it on load. In an editor built without the transpiler (use_typescript_transpiler=no, the default), the SWC export path compiles out entirely and exports pack the tsc-emitted .js exactly as today.
  • No more hard tsc gate. The editor's Start button no longer refuses to run just because tsc isn't installed.
  • Runtime resolution on no-transpiler targets. Web and mobile templates have no embedded transpiler, so they must run the pre-transpiled .js. Godot still packs the raw .ts (it's the referenced Script resource), so the resolver now prefers the pre-transpiled .js sibling for a .ts reference on those builds — autoloads and direct .ts references load instead of erroring with "cannot load .ts at runtime". Builds with the embedded transpiler are unchanged.

Caveat — checked

Every reference style had to hit that rewrite inside a real exported build (autoloads, direct .ts attaches, extension-less imports). This is handled in the resolver across three sites: check_absolute_file_path and the get_source_info absolute-path fallback each prefer the converted .js under #if !JSB_WITH_TYPESCRIPT_TRANSPILER; check_implicit_source_path keeps probing .ts on both flag states, with only the .js-preference in the !JSB_WITH_TYPESCRIPT_TRANSPILER branch (so a transpiler build still loads .ts directly). Verified end-to-end on a web export: a freshly exported web build whose autoload is a .ts boots and runs with no "cannot load .ts" error.

How to verify

Export a small project that uses a .ts autoload, run the exported web build (no transpiler present), and confirm it loads and the autoload runs. Separately, with tsc uninstalled, confirm the editor Start button still runs.

A changeset is included. (Opened on its own, this PR's base is 6-swc-sourcemaps; see the README for how the stack is delivered.)

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.
The FFI now returns an inline data:application/json;base64 sourcemap URL, which the resolver appends to the wrapped module source as a //# sourceMappingURL comment and also decodes into a per-environment source-map cache. A V8 PrepareStackTraceCallback rewrites runtime Error.stack frames to .ts line/col through that cache, since V8 12.4 only applies source maps in DevTools and not to Error.stack.
…anspiler targets)

Export targets without the embedded SWC transpiler (web/mobile) can't compile
.ts at runtime. Pre-transpile .ts -> .js via SWC at export time so exported
builds need no `tsc`, drop the hard tsc-install gate on the editor Start button,
and resolve a .ts module to its pre-transpiled .js sibling at runtime.

The export plugin writes the .js to convert_typescript_path() (res://.godot/
GodotJS/<path>.js). Godot still packs the raw .ts (it is the referenced Script
resource), so DefaultModuleResolver would otherwise match the un-loadable .ts
first; on !JSB_WITH_TYPESCRIPT_TRANSPILER builds it now prefers the .js sibling
in check_absolute_file_path (autoloads / direct .ts attaches) and
check_implicit_source_path (extension-less imports). Transpiler builds (editor +
desktop) are byte-identical; falls through to the .ts (and the guard error)
when no .js is packed.

An editor built without the transpiler (use_typescript_transpiler off, the
default) compiles the SWC export path out entirely and packs the tsc-emitted
.js exactly as before this stack.
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