Draft / April 22, 2026
TSRX
This page is the working language specification for TSRX. It is written for implementors of parsers, language tooling, compilers, and hosts that need a precise account of the syntax and static constraints of the language.
First-edition scope
The current draft fixes the syntax and early-error surface for component declarations, statement-oriented template elements, expression islands, lazy destructuring, raw style elements, {style} attribute values, and proposal-aligned submodule declarations.
Introduction
TSRX is a TypeScript-compatible syntax extension to ECMAScript for authoring component-oriented user interface programs. It extends the TypeScript source language with a function-like component construct, statement-oriented template elements, explicit TSX expression islands, lazy destructuring patterns, raw style elements, and style identifiers.
This specification defines the syntax of TSRX and the static constraints that a conforming implementation is expected to enforce. It does not propose incorporation of TSRX into the ECMAScript standard, and it does not require JavaScript engines or browsers to parse TSRX directly. Instead, it defines a source language intended for compilers, preprocessors, editors, formatters, and other tooling.
Unless this document explicitly states otherwise, a construct that is valid in the TypeScript baseline grammar remains valid in TSRX source text. The grammar additions in this document are therefore additive and TypeScript-compatible.
Rationale
TSRX exists to define a predictable syntax for component-oriented source code without forcing all tree-shaped UI through the expression positions of JSX. In TSRX, template structure is explicit in the component body, and expression-position UI values are explicit through TSRX or TSX islands.
- Component bodies may contain element statements directly.
- JSX-compatible values remain available, but only through explicit island syntax.
- Lazy destructuring is represented as source syntax rather than a host-specific library convention.
- Host features such as server execution and stylesheet composition can be specified cleanly on top of a shared grammar.
1 Conformance
An implementation of core TSRX conforms to this specification if it accepts TypeScript source text together with the additional TSRX constructs defined here, rejects source texts that violate the listed early-error rules, and documents any host-defined or profile-defined semantics that are not fixed by the core language.
This specification distinguishes between core TSRX and host profiles. Core TSRX defines the syntax and static rules common to all conforming implementations. A host profile may add profile-specific constructs or strengthen restrictions on core constructs.
2 Notational Conventions
The syntactic and lexical grammar of ECMAScript are incorporated by reference as already extended by a TypeScript-compatible baseline grammar. When this document presents a modified production, it should be read as a delta against that baseline rather than as a complete restatement of the surrounding grammar.
3 Modified Lexical Conventions
TSRX introduces a context-sensitive component keyword, the {style} attribute directive, the whitespace-sensitive lazy-pattern introducers &{ and &[, and module declarations that can be imported by identifier source.
The ampersand introducer is part of the syntax of the lazy pattern itself. Implementations must not treat & { or & [ as lazy pattern forms.
4.1 Modified Productions
The following productions define the normative core additions for the first edition. They are additive over the TypeScript-compatible baseline grammar used by TSRX implementations.
PrimaryExpression :
ComponentExpression
TsrxExpression
TsxExpression
StatementListItem :
ComponentDeclaration
ElementStatement
LazyAssignmentStatement
BindingAtom :
LazyObjectBindingPattern
LazyArrayBindingPattern
LazyObjectBindingPattern :
&{ BindingPropertyListopt }
LazyArrayBindingPattern :
&[ BindingElementListopt ]
TsrxExpression :
<tsrx> TemplateChildrenopt </tsrx>
TsxExpression :
<tsx> TsxChildrenopt </tsx>
<tsx: IdentifierName> TsxChildrenopt </tsx: IdentifierName>
<> TsxChildrenopt </>4.2 Components
A component is function-like in its parameter and type-parameter surface, but its body is extended with TSRX template forms. In the first edition, only declaration-form and expression-form components are part of the normative core surface.
ComponentDeclaration :
component BindingIdentifier ( FormalParametersopt ) { ComponentBody }
ComponentExpression :
component BindingIdentifieropt ( FormalParametersopt ) { ComponentBody }Component methods in object literals and class bodies are accepted by the current reference parser, but they are treated as deferred syntax in this first-edition draft rather than as normative core grammar.
4.3 Template Elements
Within a component body, element forms are admitted as template statements rather than as generic expressions. This statement-oriented model is normative. A tree that contributes directly to a component body is not interchangeable with an ordinary expression value.
Template position also admits direct double-quoted static text children and braced expression forms. The ordinary form { AssignmentExpression } contributes a generic template-expression node. The keyword-prefixed forms {text AssignmentExpression} and {html AssignmentExpression} request escaped-text and raw-markup handling respectively.
DoubleQuotedTextChild :
" JSXStringCharactersopt "
TemplateExpression :
{ AssignmentExpression }
{ text AssignmentExpression }
{ html AssignmentExpression }- A direct double-quoted text child is represented by
Textwith a string literal expression and contributes escaped static text. Its quoted contents use the same lexical rules as a quoted JSX attribute value: character references such as"are decoded, and backslashes do not escape the closing quote. { AssignmentExpression }is the generic template-expression form and is represented byTSRXExpressionin the current parser.{text AssignmentExpression}is represented byTextand forces escaped text-node handling.{html AssignmentExpression}is represented byHtmland requests host-defined raw-markup handling.- The text and html keywords are only distinguished in this braced template-expression position; elsewhere they remain ordinary identifiers.
4.3.1 Whitespace
Whitespace in template syntax follows ordinary ECMAScript token-separation rules except at a small number of JSX-like boundaries where the delimiter itself is whitespace-sensitive.
- Within
{ AssignmentExpression },{text AssignmentExpression}, and{html AssignmentExpression}, whitespace and line terminators are admitted wherever the embedded ECMAScript grammar permits them. Thus forms such as{ expr },{ text expr }, and{ html expr }are well-formed. - A tag or fragment introducer must be written as a contiguous delimiter sequence. Implementations must not treat
< div>,< /div>,</ div>,< tsx>,< tsx:react>,< >, or< / >as TSRX template delimiters. - Whitespace and comments are not admitted within a single tag name or compatibility discriminator. Source texts such as
<Foo . Bar>or<tsx : react>do not form a single TSRX tag name. - Indentation and line breaks between adjacent template statements are permitted as layout. They do not require explicit expression containers merely to separate one element statement from the next.
The raw style element is also part of this syntax. A style body is captured as raw text for host-defined stylesheet processing rather than being parsed as nested template children.
4.4 Expression Islands
TSRX does not admit arbitrary template or JSX elements in expression position. When a tree must participate in ordinary expression evaluation, it must be written as an expression island. The <tsrx> form keeps native TSRX template children, while TSX islands use JSX-compatible children.
TsrxExpression :
<tsrx> TemplateChildrenopt </tsrx>
TsxElement :
<tsx> TsxChildrenopt </tsx>
TsxCompatElement :
<tsx: IdentifierName> TsxChildrenopt </tsx: IdentifierName>
TsxFragmentShorthand :
<> TsxChildrenopt </>The fragment shorthand form is equivalent to a <tsx>...</tsx> form in expression position. Native TSRX template islands use <tsrx>...</tsrx>. Compatibility islands through <tsx:kind> are part of core TSRX syntax, although the meaning of kind is host-defined.
4.5 Lazy Destructuring
TSRX extends the baseline binding grammar with lazy array and object forms. The syntax is fixed by the language. The runtime observation model is not. Hosts may implement lazy bindings as deferred property reads, reactive accessors, or another equivalent mechanism, provided the syntactic and static constraints remain satisfied.
LazyAssignmentStatement :
LazyObjectBindingPattern = AssignmentExpression ;
LazyArrayBindingPattern = AssignmentExpression ;4.6 Style Elements and Style Identifiers
The syntax of raw style elements and {style} attribute values is part of core TSRX. The host is responsible for the meaning of selector scoping, generated class names, selector eligibility, and stylesheet registration.
StyleAttributeExpression :
{ style StringLiteral }4.7 Host-defined Server Extensions
Submodule declarations are documented in the first edition as a generic extension surface aligned with the TC39 module declarations proposal. Ripple currently defines one host profile for this surface: module server declarations and imports from server.
SubmoduleDeclaration :
module Identifier { ModuleItemListopt }
SubmoduleImportDeclaration :
import ImportClause from Identifier ;
5 Static Semantics: Early Errors
- A template or JSX element other than an expression island must not appear as a generic ECMAScript expression.
- A JSX fragment must not appear in template position outside a TSX island context.
- A tag or fragment delimiter must not be split by intervening whitespace at the points described in 4.3.1.
{text }and{html }are not well-formed and must be rejected.<tsx />and<tsrx />and<tsx:kind />are not well-formed and must be rejected.- Opening and closing tags for expression islands must match.
- {style StringLiteral} must only be used as a whole element attribute value.
- In hosts that enable server-oriented submodules, server exports must be imported before use, for example import { load } from server.
- Host profiles may restrict which submodule names are supported and may impose additional restrictions on referenced bindings.
- Element statements must not appear outside a component body.
6 Host-defined Semantics
The purpose of the core specification is to make parsers, tooling, and language consumers agree on what TSRX source text means as syntax. The purpose of host documentation is to explain how that syntax is executed, lowered, or bound to runtime facilities.
- How components lower into executable host code.
- How lazy destructuring is realized at runtime.
- How {style} names resolve to generated or scoped class names.
- How compatibility kinds are configured for
<tsx:kind>forms. - How submodule declarations and imports from identifier sources are compiled or executed by a host profile that enables them.
Appendices
The current reference implementation exposes ESTree-compatible nodes such as Component, Tsx, TsxCompat, Element, TSRXExpression, Text, Html, Style, TSModuleDeclaration, TSModuleBlock, Attribute, RefAttribute, and SpreadAttribute. The grammar in sections 4.1 through 4.7 is normative; the node shapes in this appendix are informative and describe the parser contract currently exposed to tooling.
A.1 Grammar-to-node correspondence
The reference parser follows the same broad editorial pattern used by the JSX specification: grammar productions define the accepted source forms, and a separate AST layer records those forms in a stable shape for downstream tools. The following correspondence summarizes the first-edition mappings.
Informative grammar-to-node correspondence
ComponentDeclaration, ComponentExpression -> Component
ElementStatement -> Element
{ AssignmentExpression } in template position -> TSRXExpression
{ text AssignmentExpression } in template position -> Text
{ html AssignmentExpression } in template position -> Html
JSXAttributeName JSXAttributeInitializeropt -> Attribute
{ ... AssignmentExpression } in attribute position -> SpreadAttribute
ref={ AssignmentExpression } -> RefAttribute
<tsrx> TemplateChildrenopt </tsrx> -> Tsrx
<tsx> TsxChildrenopt </tsx> -> Tsx
<> TsxChildrenopt </> -> Tsx
<tsx: IdentifierName> TsxChildrenopt </tsx: IdentifierName> -> TsxCompat
{ style StringLiteral } in attribute position -> Style
module Identifier { ModuleItemListopt } -> TSModuleDeclaration
import ImportClause from Identifier -> ImportDeclaration with Identifier sourceA.2 Component nodes
Both ComponentDeclaration and ComponentExpression are represented by a single Component node. This follows the function-like view of components used throughout the grammar while still preserving component-specific body content and stylesheet attachment.
interface Component {
type: 'Component';
id: Identifier | null;
params: Pattern[];
body: Node[];
css: CSS.StyleSheet | null;
default: boolean;
typeParameters?: TSTypeParameterDeclaration;
}- The id field is null for anonymous component expressions and otherwise preserves the declared component name.
- The params field preserves the original formal parameter list, including TypeScript annotations and lazy patterns after parsing.
- The body field stores ordinary statements and template nodes in source order rather than lowering template content into a separate subtree.
- The css field stores the parsed component stylesheet when a non-head raw style element appears within the component body.
- The default flag records whether the component occupied a default-export position in the original source.
- Implementation metadata may additionally record topScopedClasses and styleClasses for downstream analysis.
A.3 Template and attribute nodes
Template statements are represented directly in the AST rather than through a JSX element wrapper. This appendix distinguishes the element node itself from the attribute nodes that refine its opening tag.
interface Element {
type: 'Element';
id: Identifier | MemberExpression;
attributes: Array<Attribute | SpreadAttribute | RefAttribute>;
children: Node[];
openingElement: JSXOpeningElement;
closingElement: JSXClosingElement;
selfClosing?: boolean;
unclosed?: boolean;
}
interface TSRXExpression {
type: 'TSRXExpression';
expression: Expression;
}
interface Text {
type: 'Text';
expression: Expression;
}
interface Html {
type: 'Html';
expression: Expression;
}
interface Attribute {
type: 'Attribute';
name: Identifier;
value: Expression | null;
shorthand?: boolean;
}
interface RefAttribute {
type: 'RefAttribute';
argument: Expression;
}
interface SpreadAttribute {
type: 'SpreadAttribute';
argument: Expression;
}- Element.id is an Identifier for ordinary tag names and a MemberExpression when the source used a namespaced or dotted element form.
- openingElement and closingElement preserve the original tag delimiters so formatters and source-mapping tools can recover the authored shape.
- selfClosing and unclosed record parser state for self-closing syntax and loose-mode recovery.
- TSRXExpression wraps the embedded ECMAScript expression from the ordinary {expr} template form.
- Text wraps either a direct double-quoted static text child or the embedded ECMAScript expression from the explicit {text expr} form, and marks it for escaped text handling. Static text children decode JSX character references before the literal expression is stored.
- Html wraps the embedded ECMAScript expression from the explicit {html expr} form and marks it for host-defined raw-markup handling.
- Attribute.value is null for boolean-style attributes with no initializer, while shorthand marks parser-recognized shorthand cases.
- RefAttribute.argument and SpreadAttribute.argument each preserve the original ECMAScript expression payload carried by the attribute form.
- Current implementations may additionally attach style-element-specific details such as captured stylesheet source text or styleScopeHash when the element is a raw
<style>tag, but those details are not part of the general Element shape described here.
A.4 Expression island nodes
Expression-position islands use distinct node kinds so tools can distinguish ordinary template elements, native TSRX subtrees, and JSX-compatible subtrees. The <tsrx> form is represented as Tsrx, the TSX fragment shorthand is represented as Tsx, and compatibility islands with an explicit kind are represented as TsxCompat.
interface Tsrx {
type: 'Tsrx';
attributes: Array<any>;
children: Node[];
openingElement: JSXOpeningElement;
closingElement: JSXClosingElement;
selfClosing?: boolean;
unclosed?: boolean;
}
interface Tsx {
type: 'Tsx';
attributes: Array<any>;
children: JSXChild[];
openingElement: JSXOpeningElement;
closingElement: JSXClosingElement;
selfClosing?: boolean;
unclosed?: boolean;
}
interface TsxCompat {
type: 'TsxCompat';
kind: string;
attributes: Array<any>;
children: JSXChild[];
openingElement: JSXOpeningElement;
closingElement: JSXClosingElement;
selfClosing?: boolean;
unclosed?: boolean;
}- Tsrx corresponds to <tsrx> ... </tsrx> when that form appears in expression position. Its children array follows the native TSRX template-child model.
- Tsx corresponds to both <tsx> ... </tsx> and the fragment shorthand <> ... </> when those forms appear in expression position.
- TsxCompat corresponds to <tsx:kind> ... </tsx:kind>, and the kind field stores the compatibility discriminator exactly as parsed.
- Tsx and TsxCompat children arrays follow the JSX child model inherited from the ESTree JSX node family rather than TSRX template-child normalization.
- openingElement, closingElement, selfClosing, and unclosed serve the same recovery and source-preservation role as on Element nodes.
A.5 Submodules, special identifiers, and stylesheets
TSRX reuses TypeScript-compatible module declaration node shapes for submodules and extends ImportDeclaration sources so a source may be an Identifier. These nodes are intentionally narrow: most of their semantics come from surrounding grammar or from host-defined analysis, not from a wide intrinsic property surface.
interface Style {
type: 'Style';
value: Literal;
}
interface TSModuleDeclaration {
type: 'TSModuleDeclaration';
id: Identifier;
body: TSModuleBlock;
}
interface TSModuleBlock {
type: 'TSModuleBlock';
body: Array<Statement | ImportDeclaration | ExportNamedDeclaration>;
}
interface CSSStyleSheet {
type: 'StyleSheet';
children: Array<Atrule | Rule>;
source: string;
hash: string;
}- Style corresponds to the {style StringLiteral} attribute directive.
- TSModuleDeclaration represents module Identifier { ... } submodules.
- ImportDeclaration.source may be an Identifier for imports from a declared submodule.
- Ripple records exported names from module server declarations for downstream RPC analysis.
- CSSStyleSheet is attached through Component.css and stores both the original stylesheet source text and the stable hash used by current host implementations for scoping.
The reference parser is built on Acorn with @sveltejs/acorn-typescript and extended by a custom TSRXPlugin. This is why the grammar in this draft is framed as additive over a TypeScript-compatible baseline rather than as a language unrelated to TypeScript source syntax.
The parser currently accepts component methods in object literals and class bodies. Those forms are treated as deferred syntax for the first edition of this specification.