Skip to content

feat: expose ast utilities in typescript_wasm#11973

Open
avivkeller wants to merge 1 commit into
swc-project:mainfrom
forked-by-avivkeller:ast
Open

feat: expose ast utilities in typescript_wasm#11973
avivkeller wants to merge 1 commit into
swc-project:mainfrom
forked-by-avivkeller:ast

Conversation

@avivkeller

Copy link
Copy Markdown

Currently, https://github.com/nodejs/node relies on two separate AST parsers, as SWC does not expose it's AST bindings to JS-land. This PR changes SWC to expose those bindings, allowing for consumers, like node core, to parse AST using SWC.

See: nodejs/amaro#435

@avivkeller avivkeller requested a review from a team as a code owner June 29, 2026 13:51
@changeset-bot

changeset-bot Bot commented Jun 29, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: c472f99

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@CLAassistant

CLAassistant commented Jun 29, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c472f99e7c

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

None => {
if input.is_instance_of::<Uint8Array>() {
let input = input.unchecked_into::<Uint8Array>();
match input.to_string().as_string() {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Decode Uint8Array bytes before parsing

For the newly exported parse APIs, ordinary Uint8Array input is converted with TypedArray.prototype.toString(), which joins bytes as comma-separated numbers rather than decoding UTF-8. In Node/browser callers that pass new Uint8Array(...) or TextEncoder().encode(src), parseSync will parse strings like 99,108,97... and can return a wrong AST or syntax error instead of parsing the original source; decode the byte array contents explicitly.

Useful? React with 👍 / 👎.

.emit();
}

Ok(program)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Run resolver before returning parsed AST

The existing JS-facing parse bindings resolve scopes before serializing (build_parse_sync! in crates/binding_macros/src/wasm.rs and bindings/binding_core_node/src/parse.rs both call resolver(...)), but this new API returns the raw parser result. For callers that inspect ctxt to distinguish unresolved/top-level/local identifiers or compare with @swc/core.parseSync, even simple declarations are wrong because all identifiers keep ctxt: 0; run the resolver before Ok(program).

Useful? React with 👍 / 👎.

Comment on lines +33 to +34
export declare function parse(src: string | Uint8Array, opts?: Options): Promise<Program>;
export declare function parseSync(src: string | Uint8Array, opts?: Options): Program;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Expose parser options in parse typings

These new parse signatures point TypeScript users at Options, but the exported Options interface from swc_ts_fast_strip does not declare the parser field that parse_program actually reads. In typed consumers that need TSX parsing, parseSync('<x />', { parser: { tsx: true } }) is rejected even though TsSyntax::tsx defaults to false and the runtime supports the option; add parser options to the public type or use a parse-specific options type.

Useful? React with 👍 / 👎.

@codspeed-hq

codspeed-hq Bot commented Jun 29, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 189 untouched benchmarks
⏩ 61 skipped benchmarks1


Comparing forked-by-avivkeller:ast (c472f99) with main (060c7ac)2

Open in CodSpeed

Footnotes

  1. 61 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

  2. No successful run was found on main (8ada196) during the generation of this report, so 060c7ac was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

@kdy1 kdy1 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can we do rust-side transform/analysis code instead of js side code?
Asking because it would be really slow to pass AST from Rust to js

@kdy1

kdy1 commented Jun 30, 2026

Copy link
Copy Markdown
Member

If it's really inevitable I can merge this PR, but I think it's not a good idea for performance

@avivkeller

Copy link
Copy Markdown
Author

Post nodejs/node#64034 (which removes our dependency on AST parsing for part of the REPL), we use AST parsing to:

  • Get the first expression in a code string based on a column (used for determining where an assertion error came from)
  • Transform static imports to dynamic ones
  • Determine if JS syntax is unterminated, and therefor recoverable
  • Determine if JS syntax is valid

We can probably determine if JS syntax is valid + recoverable (in the REPL) via CDP's Runtime.compileScript (which'll determine the validity of syntax). (See chrome's implementation of IsExpressionComplete)

For me, it feels weird to expose Node.js-internal-use-only Rust bindings (like getFirstExpression or transformImports), but WDYT? I can also get some Node.js-ers opinions.

@kdy1

kdy1 commented Jul 1, 2026

Copy link
Copy Markdown
Member

I made #11975 based on the requirements of nodejs/node#64034

Note: I made it with Codex, so I need to verify it. I'm busy atm, so I can verify later this week

@kdy1

kdy1 commented Jul 3, 2026

Copy link
Copy Markdown
Member

@avivkeller Does the API in #11975 looks good to you?

@kdy1

kdy1 commented Jul 11, 2026

Copy link
Copy Markdown
Member

@avivkeller As an alternative, I can create a nodejs-only binding wasm. What do you think about it?

@avivkeller

Copy link
Copy Markdown
Author

Sorry for the delay here! Been extremely swamped, thanks for the second ping. I'd love for some Node.js-ers to give their opinions before we proceed with anything, cc @marco-ippolito @JakobJingleheimer

@marco-ippolito

Copy link
Copy Markdown
Contributor

I dont mind the experiment we can release a nightly of amaro

@avivkeller

Copy link
Copy Markdown
Author

@kdy1 both ideas look good to me, so which ever you prefer

kdy1 added a commit that referenced this pull request Jul 13, 2026
**Description:**

Adds an independent Rust/WebAssembly binding at
`bindings/binding_nodejs_support_wasm`, published as
`@swc/nodejs-support-wasm`.

The package has its own Rust crate, sources, build scripts, tests,
README, Cargo entry, and npm workspace entry. It does not build from
`binding_typescript_wasm` and does not add features or exports to the
existing `@swc/wasm-typescript` or `@swc/wasm-typescript-esm` packages.

The new CommonJS package embeds its wasm binary and exposes these APIs
at the top level:

- `transform`
- `transformSync`
- `transformModuleSyntax`
- `getFirstExpression`
- `isValidSyntax`
- `isRecoverableError`

It also exports the related `Options`, `TransformOutput`, and
`ModuleSyntaxTransformOutput` TypeScript types.

The npm publish matrix, wasm CI matrix, release versioning, Cargo
lockfile, and pnpm lockfile now reference the standalone
`binding_nodejs_support_wasm` crate.

Validation performed:

- `git submodule update --init --recursive`
- `cargo fmt --all -- --check`
- `cargo test -p binding_typescript_wasm -p binding_nodejs_support_wasm`
- `cargo clippy --all --all-targets -- -D warnings`
- `(cd bindings/binding_nodejs_support_wasm && ./scripts/test.sh)`
- `(cd bindings/binding_typescript_wasm && ./scripts/test.sh)`
- `(cd bindings/binding_core_wasm && ./scripts/test.sh)`
- `(cd bindings/binding_minifier_wasm && ./scripts/test.sh)`
- `(cd bindings/binding_es_ast_viewer && ./scripts/test.sh)`
- `(cd bindings/binding_nodejs_support_wasm/pkg && npm pack --dry-run
--json)`
- `pnpm install --frozen-lockfile --ignore-scripts`
- `go run github.com/rhysd/actionlint/cmd/actionlint@latest
.github/workflows/CI.yml .github/workflows/publish-npm-package.yml`

**BREAKING CHANGE:**

None. The existing TypeScript wasm packages and their public APIs are
unchanged.

**Related issue (if exists):**

- Supports nodejs/node#64034
- Follow-up context from #11973
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

4 participants