Skip to content

Add a playground that shows .rx as Solid 2.0 primitives - #70

Draft
brenelz wants to merge 6 commits into
solidjs:mainfrom
brenelz:playground-wasm-app
Draft

brenelz wants to merge 6 commits into
solidjs:mainfrom
brenelz:playground-wasm-app

Conversation

@brenelz

@brenelz brenelz commented Nov 14, 2026 •

Copy link
Copy Markdown

A browser playground for learning what ReactiveScript compiles to: type .rx on the left, and the right side shows the same program written with Solid 2.0's own primitives (createSignal, createMemo, createOptimistic, createProjection, action, <Errored>, <Loading>), with the checker's diagnostics underneath. Work in progress; it is not deployed.

The playground: .rx on the left, the same program as Solid 2.0 primitives on the right

Compiler change

The language server's WebAssembly build (src/wasm.rs) already answers analyze and prelude over a host rx_read, so it can run from memory in a browser. It could not compile, because app::app reads the program from disk. Two small changes make it possible:

  • app::app_with takes a reader in place of the disk; app::app (the Node-API build and rxc app) calls it with read_disk and behaves as before.
  • serve.rs answers an app request ({entry, overlays, target, moduleName, spa, jsx}) through it. With jsx: true, each module also carries the JSX handed to Solid's compiler, printed by the new emit::view_jsx over oxc_codegen (already in the lockfile through solidjs-compiler, now a direct dependency).
  • analyze takes project: false to return diagnostics without building the TSX projection, which the playground does not show. Leaving it out keeps the language server's behaviour.

The playground also turned up a parser bug that the language server shares: the recovering parse read a missing statement as an empty one without advancing, so parse_block spun on an unclosed { at the end of input. A half-typed handler such as onClick={() => { c made analyze take 8 to 27 seconds. parse_block now stops when a pass reads nothing, and the missing } is an ordinary parse error.

The napi wasm32-wasi browser build was the other candidate, but its WASI has no filesystem, so app(entry) cannot read sources there.

Playground (site/playground/)

A Vite + Solid rc.21 app. Each keystroke runs analyze (with project: false) for diagnostics and app with jsx: true for the program, under 1 ms per keystroke once the compiler is warm. The page renders before rxc.wasm arrives; the wasm is built with symbols stripped (4.8 MB), compiled while it streams, and retried if the download is cut off. The JSX is formatted with Prettier, then src/solidify.ts writes each __rt helper that is a Solid primitive underneath (spec/11 CP-4b, @reactivescript/runtime) as that primitive and strips the mangled names (v$id, w$id become id, setId). This view is a reading aid, not the code that runs. Explain notes name the .rx form behind each line. The program is kept in the URL hash.

site/.assetsignore leaves playground/ out of the static site upload, so deploying reactivescript.dev is unchanged. public/rxc.wasm is built by pnpm wasm and not checked in.

Verification

  • cargo check --manifest-path compiler/Cargo.toml --all-targets
  • cargo test --release --manifest-path compiler/Cargo.toml, including the new an_unclosed_block_at_the_end_of_input_stops.
  • Every prefix and every one-character deletion of the six examples, replayed through analyze and app: the slowest step takes 14 ms.
  • In site/playground: pnpm wasm, npx tsc -p ., pnpm build, and pnpm test with pnpm dev running: 11 Playwright checks covering the featured await lowering, Explain notes, stores, a rest prop as omit, keyed and draft projections, live updates, WRITE_TO_NON_SOURCE with its rule and guide link, every example, the URL hash, a two-module program, and no page errors.
  • One round of self-review and a Codex review; the findings were fixed (operator precedence in the == rewrite, renames inside string literals, store reads scoped to their declaring block, two-module formatting, the URL after adding a file, and others).

Open

  • Hosting: serving it at reactivescript.dev/playground needs a build step that has the wasm32-unknown-unknown target (the editor.yml workflow already builds it) and Vite's base: "/playground/".
  • The rewrite is text-based, so unusual programs can fall back to the compiler's raw JSX, with a note saying so.
  • Prop defaults stay as createMemo(() => props.x === undefined ? d : props.x) rather than merge: merge keeps an explicit undefined, while 07 §5 applies defaults on undefined.

🤖 Generated with Claude Code

app::app_with takes a reader in place of the disk, so serve.rs can build a
program from the editor's files. With jsx: true each module also carries the
JSX handed to Solid's compiler (emit::view_jsx).
@socket-security

socket-security Bot commented Nov 14, 2026 •

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addednpm/​playwright-core@​1.65.099100809980
Addednpm/​vite@​8.3.8991008298100
Addednpm/​highlight.js@​11.12.09910010085100
Addednpm/​prettier@​3.10.1991009799100
Addednpm/​@​solidjs/​vite-plugin@​3.0.0-next.579810010097100

View full report

The recovering parse reads a missing statement as an empty one without advancing, so parse_block looped until it found a closing brace. A half-typed handler like `onClick={() => { c` made analyze take 8 to 27 seconds.
… http

The page shows its header and editor while rxc.wasm downloads, the wasm is stripped (5.5 to 4.8 MB), compiled while streaming and retried on a cut-off download, and Share falls back to the copy command where the Clipboard API is missing.
The playground shows only diagnostics from analyze, so it now passes project: false; analyze per keystroke goes from 0.16 to 0.09 ms once warm.

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.

1 participant