#author("2026-08-31T22:15:08+09:00","default:guide","guide") * Pkript/文法/JSX [#j1d6268f] #author("2026-09-04T00:06:06+09:00","default:mcp","bot-mcp") * Pkript/文法/JSX [#kd8fa19f] 式中に HTML タグを直接記述する構文。 HTML要素を式として記述する記法。文字列連結の代わりに使う。 ** リファレンス [#q3400424] ** リファレンス [#x916181f] *** 構文 [#o4cbe103] *** 構文 [#ad1b5edc] #pkript(highlight){{ #highlight{{ <タグ 属性="値">{式}</タグ> }} - 設定定数 PKRIPT_JSX が 1 の場合に利用可能 - {} 内に渡された文字列は自動で HTML エスケープ処理が行われる - JSX 要素同士を {} 内に埋め込んだ場合、エスケープされず要素の入れ子として展開される - 単一の親要素、またはフラグメント <> ... </> で囲む必要がある - 設定定数 PKRIPT_JSX が 1(既定)の場合に利用できる。0 にすると < は比較演算子としてだけ働く - {} 内に渡された文字列は自動でHTMLエスケープされる - JSX要素どうしを {} 内に埋め込んだ場合はエスケープされず、入れ子としてそのまま展開される - 単一の親要素、またはフラグメント <> ... </> で囲む #highlight{{ function plugin_hello_convert(e) { const name = e.args[0]; return <p class="greeting">こんにちは、{name} さん</p>; } }} ** 使用法 [#e13a22b8] *** 書ける形 [#p616b045] *** 変数を埋め込んでコンポーネントを出力する [#jefdc937] **** 要素と入れ子 [#form_nest] #pkript(highlight){{ #highlight{{ return <ul><li>a</li><li>b</li></ul>; }} **** 空要素 [#form_void] #highlight{{ return <br />; }} 閉じスラッシュが要る。<br> だけでは閉じタグ待ちになる。 **** フラグメント [#form_fragment] 複数の要素を、囲む要素なしで並べる。 #highlight{{ return <><p>a</p><p>b</p></>; }} **** 属性 [#form_attr] 文字列か {式} を書く。 #highlight{{ const cls = e.opts["class"]; return <p class="fixed">x</p>; return <p class={cls}>x</p>; return <p title={42}>x</p>; }} - className と書いても class になる - 値が null の属性は出力されない - {...obj} のスプレッド属性には対応していない **** {} の中身 [#form_expr] 式なら何でも書ける。 #highlight{{ return <div><span>{1 + 1}</span></div>; return <ul>{items.map((x) => <li>{x}</li>).join("")}</ul>; }} null, false, 空文字列は何も出力しない。0 は "0" と出る。 *** エスケープの規則 [#s4d26f32] ここがJSXの要点。文字列は必ずエスケープされ、JSX要素はされない。 #highlight{{ const s = "<li>x</li>"; // ただの文字列 return <ul>{s}</ul>; // <ul><li>x</li></ul> const el = <li>x</li>; // JSX要素 return <ul>{el}</ul>; // <ul><li>x</li></ul> }} wiki.convert() の戻り値もエスケープされない。Wiki記法から作ったHTMLをそのまま埋め込める。 #highlight{{ return <div>{wiki.convert(e.body)}</div>; }} **** htmlsc は使わない [#escape_htmlsc] {} の中は自動でエスケープされる。htmlsc() を重ねると二重エスケープになる。 #highlight{{ return <p>{htmlsc("<b>")}</p>; // &lt;b&gt; と出てしまう return <p>{"<b>"}</p>; // <b> が正しい }} **** 属性値も同じ [#escape_attr] 属性値に入れた文字列はエスケープされる。引用符を混ぜて属性を抜け出すことはできない。 #highlight{{ const c = "a\" onclick=\"alert(1)"; return <p class={c}>y</p>; // class ごと落とされる }} *** サニタイザとの関係 [#m9847ca9] JSXで書いたHTMLも、他の出力と同じようにサニタイザを通る。JSXは記法を短くするだけで、出せるHTMLの範囲は変わらない。 |~書いたもの|~出力|h |<script>alert(1)</script>|タグごと削除| |<p onclick="...">|onclick が削除| |<p id="a">|id="pkript-a" に変換| |<a href="javascript:...">|href が削除| |<p style="position: fixed">|position の宣言のみ削除(他の値は使える)| |<form>|action が自Wikiに補完| 詳しくは [[Pkript/設定/サニタイザ]] を参照。 ** 使用法 [#l5abaadf] *** 変数を埋め込んでコンポーネントを出力する [#usage_component] #highlight{{ function plugin_profile_convert(e) { const name = e.args[0] || "ゲスト"; return ( <div class="profile-box"> <h3>プロフィール</h3> <p>名前: {name}</p> </div> ); } }} *** 配列から要素を組み立てる [#usage_list] ** 注意点 [#pebe1d78] #highlight{{ const items = e.args; return <ul>{items.map((x) => <li>{x}</li>).join("")}</ul>; }} {} 内に htmlsc() を適用すると二重エスケープになるため直接変数を指定する。 ** 注意点 [#kf841ec8] *** 要素を文字列として切り貼りしない [#note_string] JSX要素は、出力の直前まで内部の目印として持ち回る。length や charAt の結果は、表示されるHTMLと一致しない。 #highlight{{ const el = <b>x</b>; return el.length; // 8 ではない }} 連結(+)と join() は安全。 #highlight{{ return <b>a</b> + <i>b</i>; return items.map((x) => <li>{x}</li>).join(""); }} *** タグの間の空白は消える [#note_space] #highlight{{ return <ul> <li>a</li> <li>b</li> </ul>; }} 上は <ul><li>a</li><li>b</li></ul> になる。要素の中の文字列に含まれる空白はそのまま残る。 *** HTMLコメントは書けない [#note_comment] <!-- --> は使えない。 *** < が比較演算子になる場合 [#note_lt] 直前のトークンで判断する。値や ) の後ろの < は比較演算子、それ以外は JSX の始まり。 #highlight{{ if (n < 10) { } // 比較 return <p>x</p>; // JSX }}