rescript-lang / rescript-lang/rescript
RFC: Unified Xote-based playground
Nobody has claimed this yet.
- Dominant language
- OCaml
- Stars
- 7.5k
- Forks
- 485
- Avg merge
- 1d 2h
- Merged PRs (30d)
- 55
Description
Summary
ReScript currently has two separate browser playground implementations:
- The Xote-based development playground in this repository under
packages/dev-playground. - The React-based production playground behind rescript-lang.org/try, implemented in rescript-lang/rescript-lang.org.
This RFC proposes consolidating them into one reusable Xote-based implementation while preserving the combined feature set.
[!NOTE]
packages/playgroundin this repository is not a third playground UI. It produces the browser compiler and.cmijartifacts consumed by playgrounds.
Current implementations
Xote development playground
The development playground is closely coupled to compiler development and supports:
- Live compilation
- Compiler builds from the current checkout,
master, and pull requests - Intermediate compiler output: parse tree, typed tree, Lambda, Lam, and JavaScript
- Module-system selection
- Warning configuration
- JSX and
let?options - Additional libraries
- Formatting, reset, and share links
- A lightweight custom editor built from a textarea, syntax-highlighting overlay, line numbers, and active-line highlighting
It currently does not provide:
- Program execution or captured console output
- React rendering
- CodeMirror diagnostics and hover information
- Vim mode
- Light and dark themes
- Resizable panes
- Browser-level or end-to-end tests
Production /try playground
The production playground is a large React implementation centered around:
Playground.resCompilerManagerHook.res- Shared
CodeMirror.resandRescriptCompilerApi.resbindings - The
TryRoute.reshost route - Cypress coverage
It supports:
- CodeMirror 6
- Run and auto-run
- JavaScript, output, problems, and settings panels
- Formatting and share links
- Released compiler versions
- React execution and captured console output in an iframe
- Vim mode and light and dark themes
- Module-system selection
- JSX preservation and
let? - Detailed warning flags
- Resizable panes
- Diagnostics, hovers, and type information where supported
It does not provide the development playground’s current-checkout, master, and pull-request compiler workflows or its intermediate compiler representations.
The resizable panes are implemented directly in Playground.res, without a split-pane dependency. The implementation tracks dragging, installs global mouse and touch listeners, updates percentage widths or heights, and changes orientation at a responsive breakpoint.
Feature comparison
| Capability | Development playground | Production /try |
|---|---|---|
| Live compilation | Yes | Yes |
| Format/reset/share | Yes | Yes |
| Released compiler versions | No | Yes |
Current checkout/master/PR builds |
Yes | No |
| Intermediate compiler output | Yes | No |
| CodeMirror 6 | No | Yes |
| Diagnostics/hovers/type information | No | Yes |
| Execute JavaScript | No | Yes |
| React rendering | No | Yes |
| Captured console output | No | Yes |
| Vim mode and themes | No | Yes |
| Resizable panes | No | Yes |
| Browser/E2E coverage | No | Yes |
Findings
The compiler API varies by version and source
A unified playground needs to load released, development, and pull-request compiler bundles through one abstraction. Capabilities vary across compiler versions, so UI logic should not infer support from version strings or scatter compatibility checks throughout components.
A normalized compiler adapter should expose explicit capabilities:
type capabilities = {
formatting: bool,
typeHints: bool,
debugOutput: bool,
jsxPreserve: bool,
experiments: bool,
}
Capabilities with multiple possible values should expose those values directly instead of adding more booleans. For example, the adapter can return the available intermediate-output kinds.
Compiler source identity should use normal variants with payloads where required:
type compilerSource =
| Release(Semver.t)
| Master
| PullRequest(int)
| Local
type compilerDescriptor = {
source: compilerSource,
label: string,
root: string,
}
This covers published releases, master, pull requests, and the locally built compiler without representing invalid states such as a pull-request source without a PR number. Stable IDs can be derived from the source, while labels and asset locations remain configurable by the host.
Legacy Reason syntax is out of scope. The unified playground only needs to support ReScript syntax.
Keep the existing /try URL format
A new URL format is not necessary. The existing production /try format should remain canonical for newly generated links.
The unified implementation should:
- Continue decoding existing ReScript
/trylinks. - Preserve the current production format for ordinary links.
- Add parameters only for state the format cannot currently represent, such as a pull-request compiler source or selected intermediate representation.
- Add fixtures ensuring existing ReScript
/trylinks continue to decode. - Test that newly generated links use the same canonical format.
Development-playground URLs do not require backward compatibility or migration. Legacy Reason links are also out of scope.
Xote needs an explicit disposal API for React hosting
The production route is currently React-based. A reusable Xote playground could be mounted as an island from the React route during migration, but Xote 7.1’s public View.mount and View.mountById APIs return unit and expose no public unmount or disposal handle.
Removing the host element directly would bypass Xote’s internal disposal and can leak subscriptions and event handlers.
Xote should expose something equivalent to:
let dispose = Playground.mount(container, options)
// Later:
dispose()
or a root handle with an explicit unmount method.
This lifecycle functionality belongs in Xote itself rather than in a playground-specific workaround.
Program execution should remain isolated
Xote should render the playground UI, but user programs should continue to run in an iframe. The evaluator should be retained and hardened around:
- Sandbox permissions
- Origin validation
- Message validation
- Runtime/compiler version selection
- React runtime loading
- Cleanup between runs
The UI framework and the runtime used by compiled user programs are separate concerns. An Xote playground can still execute and render React examples inside the output iframe.
Proposed architecture
1. Framework-neutral PlaygroundEngine
Extract the non-visual behavior into a reusable core:
- Compiler catalog and loader
- Compiler-version compatibility adapters
- Normalized compilation results
- Playground state and settings
/tryURL decoding and encoding- Program evaluator protocol
- Capability detection
This layer should not depend on React or Xote.
2. Xote PlaygroundView
Build one Xote UI on top of the engine, using CodeMirror 6 rather than the development playground’s custom textarea editor.
CodeMirror 6 remains the current major CodeMirror architecture. Its packages are versioned independently, so the implementation should use current compatible releases rather than treating “6” as a single package version.
The view should merge both playground feature sets:
- Editing, diagnostics, hover and type information
- Live compilation, formatting, and sharing
- JavaScript, output, problems, and settings
- Program execution, console output, and React rendering
- Released, local,
master, and pull-request compilers - Intermediate compiler representations
- Themes and Vim mode
- Resizable panes
Intermediate compiler representations should be exposed through an output selector:
- JavaScript
- Parse tree
- Typed tree
- Lambda
- Lam
JavaScript remains the default. Unsupported representations should be disabled or omitted based on the selected compiler’s declared capabilities.
3. Reusable Xote SplitPane
Replace the current React-specific resizing logic with a reusable Xote SplitPane component.
It should support:
- Horizontal and vertical layouts
- Responsive orientation changes
- Pointer events for mouse, touch, and pen input
- Minimum pane sizes
- Keyboard resizing
- An accessible separator
- Correct listener and subscription cleanup when unmounted
The current behavior is small enough that an additional split-pane dependency does not appear necessary.
4. Thin host integrations
Provide two small entry points around the same view:
- A standalone host for the compiler repository’s development playground
- A React wrapper for
/trythat mounts the Xote view and disposes it fromuseEffectcleanup
This allows the implementation to be introduced incrementally without requiring the documentation site to stop using React.
The available compiler sources should be configurable by the host:
- Production
/tryshows released versions by default. - The development host shows the local build,
master, pull-request builds, and optionally releases. - Non-release sources may remain accessible on
/trythrough explicit URLs without appearing in its normal compiler selector.
5. Canonical package ownership and naming
The shared implementation should live in this compiler repository and be published as @rescript/playground for consumption by the website.
Reasons:
- Development bundles, pull-request previews, and intermediate representations are closely coupled to compiler internals.
- Compiler API compatibility can evolve alongside the artifact producer.
- The website can consume a stable package instead of maintaining a second compiler integration.
The existing private artifact producer under packages/playground should remain, but be renamed to playground-compiler to distinguish it from the published UI package.
The public package name should not include “Xote,” since Xote is an implementation detail rather than part of its external contract.
Proposed migration
-
Define contracts and parity tests
- Inventory both feature sets as testable behavior.
- Define normalized compiler, capability, state, URL, and evaluator interfaces.
- Capture existing ReScript
/tryURLs as fixtures.
-
Extract the shared engine
- Move compiler loading, compatibility handling, URL state, and evaluation behind framework-neutral APIs.
- Keep both existing UIs operational while this is introduced.
-
Introduce CodeMirror 6 in the Xote view
- Port the production editor capabilities rather than extending the custom textarea implementation.
- Preserve diagnostics, hovers, keyboard behavior, themes, and Vim support.
-
Reach production feature parity
- Port output, problems, settings, execution, React rendering, console capture, and resizing.
- Add released versions to the unified compiler catalog.
- Retain the development-only compiler and debug-output workflows.
-
Add Xote unmount and disposal support
- Make the view safe to host inside the React route.
- Test repeated mount and unmount cycles for leaked subscriptions and listeners.
-
Replace hosts incrementally
- Replace the standalone development playground first.
- Mount the same package from
/tryafter parity and compatibility tests pass.
-
Remove the duplicate implementation
- Delete the old React playground UI once the Xote package is established in production.
- Keep only the thin website route wrapper and the compiler artifact producer.
Testing requirements
The consolidated implementation should include:
- Unit tests for compiler compatibility adapters
- Fixtures for existing ReScript
/trylinks - Tests for canonical URL decoding and encoding
- Editor integration tests for diagnostics, hovers, and formatting
- Evaluator tests for execution, console output, errors, and React rendering
SplitPaneinteraction and accessibility tests- Repeated mount and unmount tests
- Browser/E2E coverage for released, local,
master, and pull-request compiler sources - Feature-parity scenarios covering both existing playgrounds
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by reading packages/dev-playground and packages/playground, then compare the production implementation in Playground.res, CompilerManagerHook.res, CodeMirror.res, RescriptCompilerApi.res, TryRoute.res, and Playground.cy.res in rescript-lang.org. Trace the existing compiler, URL, evaluator, and mounting behavior before proposing the shared contracts. Done means a unified Xote-based playground preserves both implementations’ documented capabilities, URL compatibility, host integration, and disposal behavior with parity coverage.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cypress, react
- Domain
- developer-experience, frontend, testing-qa
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 30/100