WASM + typescript-go by SamuelMarks · Pull Request #1677 · dsherret/ts-morph · GitHub
Skip to content

WASM + typescript-go - #1677

Open
SamuelMarks wants to merge 3 commits into
dsherret:latestfrom
SamuelMarks:wasm-typescript-go
Open

SamuelMarks wants to merge 3 commits into
dsherret:latestfrom
SamuelMarks:wasm-typescript-go

Conversation

@SamuelMarks

Copy link
Copy Markdown

This doesn't need to be merged, more it's just to start a new conversation à la #1656

FWIW: I got my cdd-ts.wasm—Compiler Driven Development (CDD) being a suite of open-source compilers, each written in the language they target, for going to/fro OpenAPI [and more!]—down from 138MB to 106MB. Just by switching from typescript to typescript-go.

Description

This PR fundamentally shifts the compiler architecture of ts-morph by replacing the JavaScript typescript package with Microsoft's typescript-go port compiled to WebAssembly (Wasm).

As detailed in rfcs/RFC-0002-Go-Wasm-Compiler-Migration.md, this brings massive performance improvements (up to 60% faster cold parsing) and slashes the V8 memory overhead by keeping AST structures inside the Wasm linear memory.

Key Changes

  • Wasm Build Pipeline: Added a fully scripted, files-size optimized compilation pipeline utilizing go build and wasm-opt (Binaryen) in scripts/build-wasm.ts.
  • Memory Bridge: Implemented a robust JS-to-Wasm pointer bridge leveraging FinalizationRegistry to synchronize Go's garbage collector with V8.
  • Async Initialization: Replaced synchronous class instantiation with Project.create() to accommodate asynchronous Wasm compilation.
  • Node Proxies: Replaced native AST objects with WasmNode, WasmType, and WasmSymbol proxies that lazily query the Wasm runtime using syscall/js.
  • Testing & Tooling: Configured the existing Mocha test suite to run against the Wasm backend (npm run test:wasm) and added E2E real-world benchmarks.

Notes for Reviewers

  • The Go compiler is currently tracked as a git submodule (packages/compiler-go-source).
  • Wasm initialization gracefully supports Node.js, Deno, and Browser environments, including an inline Base64 fallback for bundlers.
  • Ensure to run npm install and deno task setup to build the new Wasm dependencies locally.

@nikelborm nikelborm left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't TSGO benefits evaporate with WASM? https://github.com/auvred/hybrid-type-aware-linting-performance#why-wasm-is-not-considered

The only audience that would require a tsgo-WASM backed ts-morph implementation that comes to mind is a couple of specific lambda runtimes and browsers that don't support anything else. I estimate there is a very small number of use cases that could leverage the WASM version. And even something like jsdocs.io needs it exclusively because of the way it's deployed right now - to Vercel functions.

And as I see it, the primary use case of ts-morph is purpose-built linters, formatters, codemods, CLIs, etc, most of which run in environments with access to faster tsgo communication options.

If wasm were to be supported, it shouldn't dictate the exposed API surface of ts-morph, and better be hidden behind a unified interface shared with ffi/ipc backed implementations

Comment thread .gitignore

@nikelborm nikelborm Jul 21, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PLAN.md is gitignored

Comment thread docs/PR_STRATEGY.md
@@ -0,0 +1,29 @@
# Pull Request Strategy

As outlined in the Comprehensive Migration Plan (`PLAN.md`), replacing the JavaScript TypeScript compiler with `typescript-go` (Wasm) involves working across two repositories. Here is the formal strategy for upstream integration.

@nikelborm nikelborm Jul 21, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR doesn't attach PLAN.md

- **Native Rust Bindings (N-API):** Rejected due to the operational overhead of distributing native binaries across platforms compared to a single universally runnable `.wasm` file.

## Implementation Details
Refer to `PLAN.md` for the multi-phase implementation pipeline, notably the `HandleRegistry` and `FinalizationRegistry` mechanisms used to bridge the GC gap between Go and V8. No newline at end of file

@nikelborm nikelborm Jul 21, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR doesn't attach PLAN.md

@SamuelMarks

Copy link
Copy Markdown
Author

@nikelborm Thanks for jumping in and for the great feedback! You raise some very valid points regarding WASM performance versus native targets.

I completely agree that for the primary use cases of ts-morph (linters, formatters, and CLIs running on developers' machines or in CI), we shouldn't be leaving native performance on the table by forcing a WASM backend. And I also agree that the underlying implementation details (FFI vs. WASM) shouldn't leak into the public API surface (e.g., forcing an async Project.create()). A unified backend interface that uses native FFI/IPC by default in Node/Deno, while falling back to WASM for web environments, is absolutely the right architectural direction.

However, I want to give some specific context on why I'm pushing for this and why the audience for a WASM-backed ts-morph is broader than just simple playgrounds or a couple of specific lambda runtimes. For my work, it's actually the foundational enabler.

I am the author of cdd-ts and cdd-web-ui, part of a suite for "Compiler Driven Development" (CDD). CDD provides bidirectional synchronization between API specifications (like OpenAPI) and code implementations (SDKs, Servers, CLIs). Unlike traditional string-templating code generators that overwrite files, cdd-ts uses deep AST/CST-aware parsing to perform "surgical patching". This means developers can hand-edit generated code, and the tool updates only the necessary AST nodes without destroying manual edits, or conversely, it can reverse-engineer structural changes from the source code back into a 100% accurate specification.

This is where WASM becomes mission-critical. cdd-web-ui is a central graphical dashboard that brings these polyglot compilers entirely into the browser. We need WASM for several crucial reasons:

  1. Privacy & Offline-First Execution: Our users are processing proprietary, highly sensitive internal API specs and source code. We cannot send their code over the wire to a cloud backend for compilation or AST diffing. Everything must run securely and entirely within the client's browser.
  2. Interactive Split-View Editing: As a user edits an OpenAPI spec, the WASM-powered compilers instantly parse, diff, and synthesize the updated SDK code in the adjacent pane in real time.
  3. Zero-Install Toolchain: Users can instantly interact with multi-language ASTs and code generators without needing to install heavy local toolchains (Node, Rust, JVM, etc.) on their machine.

By switching my tooling to a typescript-go WASM backend, I've already dropped the TS parser footprint from 138MB down to 106MB. Crucially, it slashes V8 memory overhead because the massive AST structures stay inside the Wasm linear memory, exposing only lightweight JS proxies.

So while I wholeheartedly agree that ts-morph's primary Node/Deno environments should prioritize a native FFI/IPC bridge for maximum throughput, having a robust WASM build isn't just an edge case for us—it's what enables next-generation, heavy-duty AST tooling to run natively and securely in the browser.

As mentioned in the PR description, this was mostly to get the conversation started. My next steps will focus on abstracting the bridge to allow for native FFI/IPC on the desktop/server side while preserving the WASM fallback for browser clients. I'd love to hear your thoughts on preferred IPC/FFI approaches for Go <-> Node.js bridging!

PS: Live hosted WASM demo in case you're interested https://offscale.io/cdd-web-ui - everything open-source of course

@nikelborm

nikelborm commented Jul 23, 2026 •

Copy link
Copy Markdown

This part:

Privacy & Offline-First Execution: Our users are processing proprietary, highly sensitive internal API specs and source code. We cannot send their code over the wire to a cloud backend for compilation or AST diffing.

Doesn't necessarily dictate this part:

Everything must run securely and entirely within the client's browser

Web browsers are not limited to only interacting with file systems, be it interacting with the host's native OS file system, or interacting with origin-scoped OPFS.

There are approaches like the one taken by https://npmx.dev/, which lets the user interact with a running local daemon of npmx, and NPMX's cloud never can reach any credential file stored on the device, as well as process it, while still making the user able to do all the nice interactions with private APIs and files, that are coming from the user's own machine, and not proxied through npmx's cloud, through the web interface, that's not shipped as part of any binary to the client, and is loaded as a plain https page from the cloud.

Interactive Split-View Editing: As a user edits an OpenAPI spec, the WASM-powered compilers instantly parse, diff, and synthesize the updated SDK code in the adjacent pane in real time

This part is not an achievement of WASM; any TSGO communication option in a sufficiently local context will let the dev "instantly parse, diff, and synthesize the updated SDK code in the adjacent pane in real time". And the instant part is actually achieved not by selecting any specific communication options, but by making the compiler stateful -- by enabling incremental typechecking and correctly managing the hierarchy of dependencies in a project and cross-package importing.

Lastly. Could you explain why you need the tsgo compiler packaged into WASM? Did you put all the C89, C++, C#, Ruby, Swift, etc compilers listed on cdd-web-ui into WASM as well? This is an insane amount of work, and I don't understand why you would do it when things like StackBlitz, peerd, Oh My Git, Effect.ts playground and countless others use the same approach implemented by things like webcontainers.io, CheerpX, v86, webvm which would essentially need you to do nothing on your side like maintaining unofficial tsgo-wasm connector, and make it possible to use the official tsgo:ffi/tsgo:ipc integration in this virtualized environment.

If avoiding installing any new tooling on the user's machine is one of the core values for your project, then virtualized environments are a universally accepted answer

@SamuelMarks

Copy link
Copy Markdown
Author

@nikelborm "Did you put all the C89, C++, C#, Ruby, Swift, etc compilers listed on cdd-web-ui into WASM as well? This is an insane amount of work"

Thank you! - It took f-o-r-e-v-e-r to engineer and get working in the browser. - Each compiler is written in the language it targets and is completely standalone. - Then my cdd-web-ui brings together all 13 language compilers into one fully client-side bidirectional (OpenAPI ⇄ language) browser-based code editor.

They are all whitespace and comment sensitive, and for the TypeScript target ts-morph is used extensively.

Without exception: every single component is open-source under (Apache-2.0 OR MIT) or even more permissive licenses.

(and purely client-side; as in you can host just the static files on any CDN, python3 -m http.server, etc. and it'll just work)

@nikelborm

nikelborm commented Jul 25, 2026 •

Copy link
Copy Markdown

This branch has not been deployed

No deployments
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.

2 participants