JSX

Implemented

Markup in expressions: a tag is a function call, an attribute is a function call, and nothing is special to the compiler.

JSX is surface syntax only. It is rewritten into ordinary calls before anything is hashed (like the pipe), so a JSX program has exactly the hash and types of the calls you would have written by hand, and UCM never sees JSX.

type attr = | Attr(string, string)
type node = | Text(string) | El(string, list<attr>, list<node>)

let text = (s: string): node => Text(s)
let div = (attrs: list<attr>, kids: list<node>): node => El("div", attrs, kids)
let b = (attrs: list<attr>, kids: list<node>): node => El("b", attrs, kids)
let cls = (v: string): attr => Attr("class", v)
let data_id = (v: string): attr => Attr("data-id", v)

let page = (n: string): node =>
<div cls="a" data-id={n}>
  hello <b>bold</b>
</div>
Verified (against UCM): page hashes exactly as div([cls('a'), data_id(n)], [text('hello '), b([], [text('bold')])]).

The rules

  • A tag is a call. <name attrs>children</name> calls the function name with a list of attributes and a list of children. Which function that is depends only on what name means in scope; the compiler knows nothing about HTML.
  • An attribute is a call. name={e} is name(e), name="s" is name("s"), and a bare name is name(true). A dash becomes an underscore (data-id calls data_id) and a reserved word gets a trailing underscore (type calls type_). All attributes of a tag share one list, so they share one type.
  • A component is just a function of the same shape. There is no capitalised-component rule: in React the case tells a string tag from a variable, but here every tag is a variable. Capitalised names are constructors in Clay, so a capitalised tag is an error.
  • Spread. {...more} splices in a list of attributes; the lists on either side are joined in written order.
  • Children. Text becomes text("...") (the function text in scope), {e} is the expression itself and must already be a node, and nested elements and fragments are themselves. When text has a line break, lines are trimmed and joined with one space, as in React; text on one line is kept as written.
  • Fragments. <>...</> calls fragment(children).
  • Where < is JSX. A < that starts an expression. After an expression it is still less-than.

Not yet: namespaced tags (svg:rect), comments inside a tag, entities in text, and a props record for components (the attribute functions are the props).