TSRX(TypeScript Render Extensions)は、UIを書くためのTypeScriptの言語拡張です。.tsrxファイルに書いたコンポーネントを、React・Preact・Solid・Vueなどそれぞれのフレームワーク向けのコードへコンパイルします。2026年4月にnpmへ初版が出てから半年で構文と変換の仕様が4回変わっており、6月以前の解説記事に載っている書き方は、現行の0.4系ではエラーになるか、意図しない表示になります。この記事では、2026年10月時点の@tsrx/react 0.4.5で実際にビルドした結果をもとに、現行の構文、JSXとの違い、React+Viteへの導入手順、採用を判断する基準をまとめます。
まとめ:TSRXの現在地と導入前に押さえる要点
- TSRXは独立したオープンソースプロジェクト(MITライセンス)で、React公式の機能ではありません。READMEの記載は「active beta development」です。
- 2026年10月時点の版は@tsrx/core 0.5.5、@tsrx/react 0.4.5です。@tsrx/reactは2026年4月18日の初版から115版を重ねています。
- 現行の構文は、ローカル処理を書く
@{ ... }と、テンプレート用の@if・@for・@switch・@tryが中心です。テキストはJSXと同じく引用符なしで書けます。 - テンプレート内に素の
ifを書いた旧構文はエラーにならず、文字列としてそのまま画面に出ます。古い記事やAIが出力したコードを貼るときに最も注意すべき点です。 - ReactでuseStateなどを条件分岐の中に置くと、コンパイルが通ってもフックの呼び出し規則に違反し、条件の切り替えで実行時エラーになることがあります。2026年6月9日に自動分離の仕組みが削除されました。
- Viteテンプレートの
tsc -bは.tsrxを読めずに失敗します。型チェックは@tsrx/typescript-pluginのtsrx-tscに置き換えます。
以下、TSRXの位置付けから構文の変遷、実際の書き方と導入手順の順に説明します。
TSRXの位置付け:Rippleから切り出されたUI記述用のTypeScript拡張
TSRXは、Dominic Gannaway氏がUIフレームワーク「Ripple」のために作ったテンプレート言語を、特定のフレームワークに依存しない形で切り出したものです。READMEは「TypeScript language extension for building declarative user interfaces」と説明しています。同氏はGitHubのプロフィールで、Inferno・Lexical・Rippleの作者であり、ReactとSvelteの元コアチームのエンジニアだと記載しています(2026年10月時点の所属はOpenAI)。
仕組みは2段構えです。まず@tsrx/coreが.tsrxのソースをフレームワーク非依存のAST(構文木)に変換し、次にターゲットごとのコンパイラがそれぞれのフレームワーク向けのコードを出力します。TSRX独自の仮想DOMやレンダラーは持たず、React向けならuseStateやSuspenseを使う普通のReactコンポーネントになります。リスト描画などに使う小さな補助関数だけが@tsrx/react-runtimeとして同梱されます。
| ターゲット | コンパイラ | 最新版 | 前提となる版 |
|---|---|---|---|
| React | @tsrx/react | 0.4.5 | react 18以上 |
| Preact | @tsrx/preact | 0.3.5 | preact 10以上 |
| Solid | @tsrx/solid | 0.3.5 | solid-js 2.0.0-rc.3(固定) |
| Vue | @tsrx/vue | 0.3.5 | vue 3.5以上+vue-jsx-vapor |
| Hono | @tsrx/hono | 0.2.5 | hono 4.13.7以上・5.0.0未満 |
| Ripple | @tsrx/ripple | 0.2.4 | Ripple本体に同梱 |
| Octane | octane/compiler | 0.11.0 | react 19・vite 8 |
版はいずれも2026年10月9日にnpmレジストリで確認した値です。Solidは2.0のリリース候補版に固定されているため、Solid 1.x系のプロジェクトにはそのまま入りません。VueはVapor Mode向けのJSX変換(vue-jsx-vapor)を経由します。Honoは2026年9月16日マージのPR #92で加わったターゲットで、Octaneは同氏が2026年6月にリポジトリを公開した、「Infernoの後継」をうたうUIフレームワークです。
構文の変遷:2026年5月から9月に変わった4つの点
TSRXを調べると、記事によって書き方がまったく違うことに気づきます。原因は公開直後の数か月で構文を大きく作り直したことにあります。公式ブログとGitHubの記録を並べると、変更は次の4回です。
| 日付 | 出典 | 変更内容 |
|---|---|---|
| 2026-05-29 | 公式ブログ「Simplifying TSRX」 | 独自のcomponent宣言を廃止し普通の関数に |
| 2026-06-08 | 公式ブログ「Rethinking TSRX」 | 制御構文を@if等へ、テキストはJSX表記に |
| 2026-06-09 | PR #1233 | React/Preactの条件内フック自動分離を削除 |
| 2026-09-14 | 公式ブログ「Removing lazy destructuring」 | &{ ... }の遅延分割代入を削除 |
5月29日の変更では、returnを書かない独自のcomponent宣言、<tsx>・<tsrx>の埋め込みブロック、ReactでclassをclassNameへ自動で書き換える処理が消えました。ブログは理由として、人間と企業の試用者から寄せられた声に加え、AIが生成したコードの評価で「returnが無いことや独自構文の多さが、デバッグ時の混乱を招いていた」ことを挙げています。
6月8日の変更が最も大きく、テンプレート内の制御構文に@を付ける形へ改めました。それまでの素のifやforは普通のJavaScriptと見分けがつかず、人間もLLMもbreakやcontinueを書いてしまうためです。同時に、静的なテキストを""で囲む必要もなくなり、JSXと同じ書き方に戻りました。ブログはこの時点で「ベータ段階へ移行し、重大なバグ修正を除いてコア構文の変更は想定していない」と書いています。
それでも9月14日には、Ripple向けに用意されていた遅延分割代入(&{ ... })が言語から削除されました。翌9月15日公開の@tsrx/react 0.3.0から構文エラーになります。ベータ宣言の後も構文の削除が起きている点は、採用を判断する際に重く見るべき事実です。
旧構文で書いたときの挙動(0.4.5で実測)
旧構文のコードを@tsrx/react 0.4.5のコンパイラに通すと、結果は3通りに分かれました。
| 旧構文 | 0.4.5での結果 |
|---|---|
component Hello() { ... } |
構文エラー(Unexpected token) |
function C(&{ name }) { ... } |
構文エラー(Unexpected token) |
JSX内の素のif (ok) { ... } |
エラーなし。「if (ok) {」が文字列で表示 |
危険なのは3つ目です。<div>の中に書いたif (ok) {は、現行の構文ではJSXのテキストとして解釈されます。コンパイルも型チェックも通り、画面に「if (ok) {」という文字が出て初めて気づきます。6月以前の記事のコード例や、学習データが古いAIの出力を貼り付けたときに起きやすいので、.tsrxの中で@の付かないif・forがテンプレートに入っていないかを確認してください。
現行構文の書き方:@{}・@if・@for・style
次のコードは、完了済みのタスクを表示・非表示にできるToDoリストです。React+Vite環境でビルドし、jsdom上で描画できることを確認しました。
import { useState } from 'react';
type Todo = { id: string; title: string; done: boolean };
export function TodoList({ initial }: { initial: Todo[] }) @{
const [items] = useState(initial);
const [showDone, setShowDone] = useState(true);
const visible = showDone ? items : items.filter((t) => !t.done);
<>
<button onClick={() => setShowDone(!showDone)}>
{showDone ? '完了を隠す' : '完了も表示'}
</button>
<ul>
@for (const item of visible; index i; key item.id) {
<li className="row">
{i + 1}. {item.title}
@if (item.done) {
<span className="done">済</span>
}
</li>
} @empty {
<li>タスクはありません</li>
}
</ul>
<style>
ul { list-style: none; padding: 0; }
.done { color: green; margin-left: 0.5rem; }
</style>
</>
}
関数本体の@{ ... }は「ステートメントコンテナ」と呼ばれ、前半に普通のTypeScriptの文を書き、最後にテンプレートを1つだけ置きます。末尾のテンプレートにはreturnを書きません。ただし、関数本体のガード節では早期returnを使えます。出力が複数の要素になるときは<>で1つにまとめます。テンプレートの後ろに処理を書くことはできません。
@forはindexとkeyを宣言でき、配列が空のときは@emptyの中身が表示されます。公式のFeaturesページによれば、@forの本体ではcontinue・break・returnが使えず、表示しない要素はループの前にfilterで除きます。テンプレート内では通常のforやwhileも拒否されます。
関数本体に前処理が要らない場合は、通常の関数としてreturn <p>…</p>;と書けます。通常の関数本体で前処理の後にテンプレートを置き、returnも@も書かなかった場合は、「This TSRX template output is unused」というコンパイルエラーになります。
コンパイル後のReactコード
上のコードを@tsrx/reactのコンパイラに直接通すと、次のようなReactコードが出力されました(見やすさのため一部を省略しています)。
import { map_iterable as __map_iterable } from '@tsrx/react/runtime/iterable';
import { useState } from 'react';
const TodoList__static1 = <span className="done tsrx-b14f0b25">済</span>;
const TodoList__static2 = <li className="tsrx-b14f0b25">タスクはありません</li>;
export function TodoList({ initial }: { initial: Todo[] }) {
// useState と visible の3行は元のまま(省略)
return <><button ...>...</button><ul className="tsrx-b14f0b25">{__map_iterable(
visible,
(item, i) => {
return <li className="row tsrx-b14f0b25" key={item.id}>{i + 1}. {item.title}{item.done ? TodoList__static1 : null}</li>;
},
null,
() => (() => {
return TodoList__static2;
})()
)}</ul></>;
}
@ifは三項演算子に、@forは補助関数map_iterableの呼び出しに変わります。中身が変わらない要素はTodoList__static1のようにコンポーネントの外へ定数として出されます。描画はReactが担いますが、リスト処理などではTSRX付属のランタイム補助関数も実行されます。
@switchはJavaScriptのswitch文を即時実行関数で包んだコードに、@try・@pending・@catchはReactのSuspenseとTSRX付属のエラーバウンダリ(TsrxErrorBoundary)に変換されました。非同期の読み込み中表示とエラー時の表示を、テンプレートの中で1か所に書ける点はJSXより簡潔です。
style要素のスコープとハッシュクラス
<style>に書いたCSSは、その<style>と同じ階層にある要素とその子孫にだけ効きます。コンパイラは対象の要素にtsrx-b14f0b25のようなハッシュクラスを付け、セレクタをul.tsrx-b14f0b25に書き換えます。Viteではこれが通常のCSSファイルとして出力され、実行時にCSS-in-JSのライブラリは動きません。
<style>の中は静的なCSSだけで、{式}は使えません。実行時に変える値はCSSカスタムプロパティで渡してvar()で読みます。const theme = <style>...</style>と変数に入れれば、theme.$classでハッシュクラスを子コンポーネントへ渡せます。使うかは任意で、Tailwind CSSやCSS Modulesを使い続けても構いません。
JSXとの違い:書き方の比較と2つの落とし穴
| 書きたいこと | JSX(TSX) | TSRX |
|---|---|---|
| 条件で表示を切り替える | 三項演算子・&& |
@if / @else |
| リストを描画する | .map()+key |
@for (... ; key ...) |
| 空のときの表示 | 長さで分岐 | @empty |
| 描画直前のローカル変数 | 関数の先頭か子コンポーネント | ブロック内の@{ ... } |
| コンポーネント単位のCSS | CSS Modules等を別ファイルで | 同じファイルの<style> |
| 静的テキスト | そのまま書く | そのまま書く(6月以降) |
テンプレートが上から読んだ順に表示される点がTSRXの利点です。ただし変換後はReactそのものなので、Reactのルールは変わりません。詰まりやすいのは次の2点です。
Reactの条件内フックと呼び出し順序の制約
2026年6月中旬までに出た英語の解説記事には、「@ifの中でフックを呼んでも、コンパイラが子コンポーネントへ切り出すので安全」と書かれているものがあります。この仕組みは2026年6月9日にマージされたPR #1233「Remove React/Preact conditional hooks」で削除されました。6月8日の公式ブログも、条件付きのフックを生成コンポーネントで安全にすることはしない、フックを持つ分岐は名前の付いた明示的なコンポーネントに分けるべきだ、としています。フックをトップレベルで呼ぶというReactフックの呼び出しルールは、TSRXで書いても変わりません。
0.4.5で、先頭でuseStateを1つ呼び、@if (open)の中でもう1つuseStateを呼ぶコンポーネントを書いたところ、コンパイルは警告なしで通りました。変換後のコードは条件の中でフックを呼ぶ形のままで、openをfalseからtrueに切り替えると、Reactが「Rendered more hooks than during the previous render.」を投げて描画が止まりました。条件の中にしかフックが無いコンポーネントでは、同じ切り替えでもエラーは出ませんでした。エラーが出ないことは安全の裏付けにならないので、フックを使う分岐は、別の名前付きコンポーネントに切り出して@ifの中から呼んでください。
ReactターゲットのclassName属性と型チェック
5月29日の変更で、classからclassNameへの自動変換はなくなりました。Reactターゲットで<li class="row">と書くと、そのままclass属性として出力され、開発時にReactが「Invalid DOM property」の警告(classNameを使うよう促すもの)を出します。tsrx-tscで型チェックすると、この書き方はTS2322として検出されます。Preact・Solid・Vue・Rippleでは逆にclassを使います。
React+Viteへの導入手順(0.4.5で実測)
Node.js 26.5.0、Vite 8(8.3.4)、React 19.3.0、TypeScript 6.0.3の組み合わせで、Reactプロジェクトに追加する手順を確認しました。
npm create vite@latest my-app -- --template react-ts
cd my-app
npm install
npm install @tsrx/react @tsrx/vite-plugin-react
次にvite.config.tsへプラグインを追加します。公式のGetting StartedはtsrxReact()だけを登録する例です。今回はテンプレートが生成した@vitejs/plugin-reactを残したまま、その前にtsrxReact()を加える構成で確認しました。
import react from '@vitejs/plugin-react'
import tsrxReact from '@tsrx/vite-plugin-react'
import { defineConfig } from 'vite'
export default defineConfig({
plugins: [tsrxReact(), react()],
})
あとはsrc/TodoList.tsrxを作り、既存のApp.tsxからimport { TodoList } from './TodoList.tsrx'で読み込みます。.tsrxは普通のモジュールとして.ts・.tsxから読み込めるので、既存のコンポーネントを書き換えずに1ファイルずつ試せます。表示するには、Appの戻り値に<TodoList initial={[{ id: 'a', title: '設計', done: true }]} />を追加します。この状態でvite buildが通り、ビルド結果をjsdom上で描画できました。
tsc -b が失敗する問題とtsrx-tscへの置き換え
Viteのreact-tsテンプレートはnpm run buildがtsc -b && vite buildになっています。素のTypeScriptは.tsrxを読めないので、App.tsxの読み込み行で「TS2307: Cannot find module ‘./TodoList.tsrx’」が出てビルドが止まります。@tsrx/typescript-pluginを入れると使えるtsrx-tscはtscと同じ引数を受け付けるので、置き換えるだけで済みます。
npm install -D @tsrx/typescript-plugin
# package.json の scripts.build を更新
npm pkg set 'scripts.build=tsrx-tsc -b && vite build'
置き換え後は.tsrxの中の型エラー(数値を文字列型の変数に入れる等)がTS2322として報告されることと、型エラーのないコードではビルドが通ることを確認しました。対応するTypeScriptは5.9系と6系です。Go製のTypeScript 7系で使うには、7.1のnightly版(7.1.0-dev.20260923.1以降)と@tsrx/content-mapperが必要で、安定版の7.0には対応していません。
エディタ拡張とPrettier・ESLint
VS Codeは拡張機能「TSRX Syntax for VS Code」を入れると、構文の色分け、診断、補完、整形が使えます。拡張がTypeScriptプラグインを内蔵しているため、TypeScript 5.9・6系ならtsconfig.jsonへのプラグイン登録は要りません(7.1 nightlyでは前述のcontent-mapperの宣言が別途必要です)。Zed向けはZedの拡張マーケットプレイスで配布され、JetBrains製IDE向けはREADMEの記載では審査中です。VS Code拡張のMarketplace IDはTSRX.tsrx-vscode-pluginです。
整形は@tsrx/prettier-plugin(peerDependenciesはPrettier 3.6.0以上)、静的解析はESLintと@tsrx/eslint-pluginで行い、.tsrxの解析用に@tsrx/eslint-parserも導入します。ほかにRspack、Next.jsのTurbopack(同15.3.0以上)、Bun向けのプラグインもあり、Viteを使っていないプロジェクトでも導入できます。
AIコーディング向けの設計とTSRX MCP
TSRXは、AIのコーディングエージェントが読み書きしやすいことも設計目標にしています。5月と6月の構文変更はどちらも「AIが生成したコードの評価」を踏まえたもので、@で境界を示したのも、LLMがcontinueなどJavaScriptの規則を持ち込むのを防ぐためと公式ブログは説明しています。ただし、AIが旧構文を生成するおそれがあります。参照する記事や取得した資料が古い場合もあるため、現行ドキュメントと照合してください。その対策として公式はMCPサーバーを用意しています。
claude mcp add tsrx -- npx -y @tsrx/mcp
上はClaude Codeに登録するコマンドです。Codex、Cursor、Gemini CLIでも同じ@tsrx/mcp(2026年10月時点で0.1.19)を設定でき、ChatGPTなどリモート接続向けにはhttps://mcp.tsrx.dev/mcpが公開されています。公式の手順は、まずinspect-projectでターゲットと導入済みパッケージを判定させ、生成したコードをformat-tsrx、compile-tsrx、analyze-tsrxの順に通す流れです。ただしコンパイルが通っても、素のifが文字列になる誤りは見つかりません。生成後は画面の表示まで確認させてください。
採用判断:試してよい場面と本番採用を見送る場面
2026年10月時点では、TSRXを顧客向けプロダクトの主要画面に採用するのは見送るべきです。理由は3つの事実に基づきます。版が0.x系で、@tsrx/reactは半年で115版を出しています。重大なバグ修正を除きコア構文の変更を想定しないと表明した後も、9月には構文の削除がありました。さらにSolidターゲットはリリース候補版に固定されています。破壊的変更のたびに.tsrxを書き直す工数を、本番の保守計画に組み込めるチームは多くありません。
一方で、試す価値がある場面ははっきりしています。.tsrxは既存の.tsxから読み込めるので、社内ツールや管理画面の末端のコンポーネントを1つだけTSRXで書き、合わなければTSXに戻すことができます。戻すときは、@ifを三項演算子に、@forをmapに置き換え、@{ ... }を通常の関数本体とreturnに直し、@emptyの分岐と<style>のCSSも移す必要があります。作業はコンポーネント1つ分で済みます。条件分岐とリストが入り組んだ表示部分ほど、可読性の差を比較しやすくなります。
試験導入で用意しておく検査
本記事で挙げた4つの落とし穴のうち、tsc -bの失敗とclassの書き誤りはtsrx-tscの型チェックで見つかります。一方、素のifが文字列で出る問題と条件内フックはコンパイルも型チェックも通ります。試験導入の段階で、条件の切り替えを含む画面操作のテストを1本用意しておくと安全です。版を固定する場合は、package.jsonで^0.4.5ではなく0.4.5と完全一致で指定し、上げるときは公式ブログとCHANGELOGを確認してから上げてください。
よくある質問
TSRXは何の略ですか?
TypeScript Render Extensionsの略です。TypeScriptにUIの記述を足した言語拡張で、ファイルの拡張子は.tsrxです。npmで「tsrx」という名前のパッケージを検索すると、2022年5月を最後に更新されていない無関係のパッケージが出てきます。TSRX本体は@tsrx/から始まるスコープ付きパッケージです。
TSRXはReactの公式機能ですか?
公式機能ではありません。Dominic Gannaway氏が作ったMITライセンスの独立したプロジェクトで、Reactチームが提供しているものではありません。React以外にPreact、Solid、Vue、Hono、Ripple、Octaneもコンパイル先として選べます。
既存のReactプロジェクトの一部だけにTSRXを使えますか?
使えます。.tsrxファイルは.tsや.tsxから通常のimportで読み込めるため、コンポーネント単位で混在させられます。Viteなら@tsrx/vite-plugin-reactを追加し、型チェックをtsrx-tscに置き換えれば動きます。
TSRXは本番環境で使えますか?
READMEの記載は「active beta development」で、版は0.x系です。2026年9月にも構文の削除があったため、顧客向けの本番画面への採用は勧めません。社内ツールなど、書き直しになっても影響が小さい範囲で試すのが現実的です。
RippleとTSRXの違いは何ですか?
Rippleは独自のリアクティブな仕組みを持つUIフレームワークで、TSRXはそのRippleのために作られたテンプレート言語を、フレームワークから独立させたものです。現在はRippleもコンパイル先の1つとしてTSRXを使っており、Rippleを使わずにReactなどでTSRXの構文だけを使うこともできます。