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, TSX expression islands, lazy destructuring, raw style elements, and #style access. It documents #server as a host-defined extension surface rather than as part of universal TSRX.

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 TSX islands.

  • Component bodies may contain element statements directly.
  • JSX-compatible values remain available, but only through explicit TSX 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 distinguished #style identifier, and the whitespace-sensitive lazy-pattern introducers &{ and &[. Host profiles may recognize additional distinguished forms, including #server when a host chooses to enable server-oriented extensions.

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
  TsxExpression
  StyleIdentifier

StatementListItem :
  ComponentDeclaration
  ElementStatement
  LazyAssignmentStatement

BindingAtom :
  LazyObjectBindingPattern
  LazyArrayBindingPattern

LazyObjectBindingPattern :
  &{ BindingPropertyListopt }

LazyArrayBindingPattern :
  &[ BindingElementListopt ]

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 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.

TemplateExpression :
  { AssignmentExpression }
  { text AssignmentExpression }
  { html AssignmentExpression }
  • { AssignmentExpression } is the generic template-expression form and is represented by TSRXExpression in the current parser.
  • {text AssignmentExpression} is represented by Text and forces escaped text-node handling.
  • {html AssignmentExpression} is represented by Html and 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 TSX Expression Islands

TSRX does not admit arbitrary JSX elements in expression position. When a JSX-compatible tree must participate in ordinary expression evaluation, it must be written as a TSX island.

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. 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 access is part of core TSRX. The host is responsible for the meaning of selector scoping, generated class names, selector eligibility, and stylesheet registration.

StyleIdentifier :
  #style

StyleAccess :
  #style . IdentifierName
  #style [ StringLiteral ]

4.7 Host-defined Server Extensions

The #server forms are documented in the first edition as host-defined extensions recognized by the current reference parser. They are not part of the core TSRX conformance surface. A conforming core TSRX implementation may omit #server entirely unless it also defines a host profile that enables server-oriented extensions.

ServerIdentifier :
  #server

ServerBlock :
  #server { StatementListopt }

ServerMemberAccess :
  #server . IdentifierName

5 Static Semantics: Early Errors

  • A JSX element other than a TSX 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 <tsx:kind /> are not well-formed and must be rejected.
  • Opening and closing tags for TSX islands must match.
  • #style must only be used as the object of a member access expression.
  • #style[...] must use a static string literal rather than a dynamic computed expression.
  • In hosts that enable #server, the bare #server identifier must only be used as the object of a member access expression except where it introduces a server block.
  • In hosts that enable #server, server blocks may be restricted to module level and may be subject to additional host-defined 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 #server blocks and server member access 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, ServerBlock, ServerIdentifier, StyleIdentifier, 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
<tsx> TsxChildrenopt </tsx> -> Tsx
<> TsxChildrenopt </> -> Tsx
<tsx: IdentifierName> TsxChildrenopt </tsx: IdentifierName> -> TsxCompat
#style -> StyleIdentifier
#server -> ServerIdentifier
#server { StatementListopt } -> ServerBlock

A.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 styleIdentifierPresent, 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 the embedded ECMAScript expression from the explicit {text expr} form and marks it for escaped text handling.
  • 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 TSX island nodes

Expression-position TSX islands use distinct node kinds so tools can distinguish ordinary template elements from JSX-compatible subtrees. The fragment shorthand is represented as Tsx, while compatibility islands with an explicit kind are represented as TsxCompat.

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;
}
  • 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.
  • The 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 Special identifiers, server blocks, and stylesheets

TSRX also introduces small dedicated node kinds for distinguished identifiers and for the profile-defined server block form. 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 StyleIdentifier {
  type: 'StyleIdentifier';
}

interface ServerIdentifier {
  type: 'ServerIdentifier';
}

interface ServerBlock {
  type: 'ServerBlock';
  body: BlockStatement & {
    body: Array<Statement | ExportNamedDeclaration>;
  };
}

interface CSSStyleSheet {
  type: 'StyleSheet';
  children: Array<Atrule | Rule>;
  source: string;
  hash: string;
}
  • StyleIdentifier corresponds exactly to the lexical token #style and is only valid when used as the object of a member access expression.
  • ServerIdentifier corresponds exactly to #server and is subject to the same member-access restriction except when it introduces a ServerBlock.
  • ServerBlock.body permits ordinary statements together with named export declarations, matching the current reference parser surface.
  • Implementation metadata on ServerBlock records the set of exported names observed within the block for downstream 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.

Released under the MIT License.

Copyright © 2025-present Dominic Gannaway