Skip to content

JavaScript Runtime

Use Xote's compiled runtime from JavaScript projects without ReScript JSX.

Xote is a ReScript-first library, but the package also ships JavaScript modules that can be imported by JavaScript applications.

The main difference is syntax: ReScript projects usually use Xote's JSX integration, while JavaScript projects should use the runtime constructors directly.

Install

1npm install xote

Use the Client Entry

For browser-rendered UI, import from xote/client. It contains the core rendering modules without router, SSR, hydration, or MDX code.

1import { Signal, Computed, Effect, View } from "xote/client";

Use the other entries only when a feature needs them:

  • xote/client for client-side views
  • xote/router for routing
  • xote/ssr for server rendering
  • xote/hydration for client hydration
  • xote/mdx for MDX integration

JavaScript Counter

Build Xote views with View.element(tag, attrs, events, children). Attributes are View.attr(name, value) pairs. Events are [eventName, handler] pairs.

1import { Signal, Computed, Effect, View } from "xote/client";
2
3const count = Signal.make(0);
4const doubled = Computed.make(() => Signal.get(count) * 2);
5
6Effect.run(() => {
7 console.log("Count:", Signal.get(count));
8});
9
10const increment = () => {
11 Signal.update(count, n => n + 1);
12};
13
14const app = View.element("main", [View.attr("class", "counter")], [], [
15 View.element("h1", [], [], [View.text("Counter")]),
16 View.element("p", [], [], [
17 View.text("Count: "),
18 View.signalText(() => String(Signal.get(count))),
19 ]),
20 View.element("p", [], [], [
21 View.text("Doubled: "),
22 View.signalText(() => String(Signal.get(doubled))),
23 ]),
24 View.element(
25 "button",
26 [View.attr("type", "button")],
27 [["click", increment]],
28 [View.text("Increment")],
29 ),
30]);
31
32View.mountById(app, "app");

The reactive reads happen inside Computed.make, Effect.run, and View.signalText. When count changes, the text nodes update in place.

Working with Existing Apps

Xote can be mounted into any existing DOM node:

1<div id="xote-widget"></div>
2<script type="module" src="/src/widget.js"></script>
1import { Signal, View } from "xote/client";
2
3const label = Signal.make("Save");
4
5View.mountById(
6 View.element("button", [], [["click", () => Signal.set(label, "Saved")]], [
7 View.signalText(() => Signal.get(label)),
8 ]),
9 "xote-widget",
10);

That makes JavaScript usage a good fit for small widgets, gradual adoption, and integration tests around the compiled runtime. New full applications should still prefer ReScript so the JSX and type system work together.