XMLをJSX (TSX)で文字列を直接使わずに安全に生成するための技術
やりたいこと
XMLを文字列を足し算やinterpolationで生成することもできるが静的に検査しづらいから。 Array.prototype.map()などのコンビネータが使える。
今回のsitemap.xmlを生成するためにクエリパラメタ名もハードコーディングせずにimportしたい。
サーバーサイドでもクライアントサイドでも利用可能か?
なぜJSXを使うのか?
JSXはHTMLリテラルが使えるJavaScriptだと思っている。
上記に「Array.prototype.map()などのコンビネータが使える。」と書いたが、JSXはHTMLすらも式であり値になっている世界だと考えられる。1や-84393431が数値リテラルで"hello, world"や"GET / HTTP/1.1"が文字列リテラルであるように、<h1></h1>や<br />が式として値として使える。 つまり今まで培ってきた式や値に関する知識をHTMLに対しても使うことができる。つまりHTMLを受け取る関数やHTMLを返す関数、HTMLを配列に詰め込むこともできる。文字列のHTMLを頑張って足し算したりするよりバグが入り込みづらく安心できる。
最終目的のsitemap.xml
今回はsitemap.xmlだが任意のXMLに利用できる方法のはず。 code:sitemap.xml
<?xml version="1.0" encoding="UTF-8"?>
<url>
</url>
</urlset>
href=に含まれる?lang=というパラメタ名は他のファイルからimportしていてハードコーディングされていない。
<xhtml:link ...>は今回は"en"か"ja"かの違いだけでここは.map()が使われる。
まずtsconfig.jsonのcompilerOptionsは"module": "commonjs",と "jsx": "react"を指定した。
追記: 後に"module": "commonjs"を指定するとDynamic importをしていてもCode splittingが効かなくなるので、ts-nodeのオプションで"module": "commonjs"に設定したtsconfig.jsonを読み込むように変更した。 実際の変更:
以下のようにReactをdevDependenciesとしてインストールする。 クライアントサイドで動的にXMLを生成するなら-Dは-S。 code:console
$ npm i -D react react-dom @types/react @types/react-dom
以下が必要なimport。
ReactはReact.createElement()するために必要。
renderToStringはReactElement型を受け取って文字列としてXMLを吐き出してくれる関数。 code:tsx
import * as React from 'react';
import {ReactElement} from "react";
import {renderToString} from 'react-dom/server';
次が重要。以下のdeclareが必要になる。
今回のXMLでは<urlset>や<url>などのタグを使うが以下がないとコンパイルエラーをする。 調べるとdeclare global {}で囲まない出てくるがそれでは上手くいかないので以下のようにした。
code:tsx
declare global {
namespace JSX {
interface IntrinsicElements {
}
}
}
<xhtml:link>など:が含まれると直接JSXで書くことができない。他にもJSXでは使用できない文字がタグに含まれている場合は以下の方法が使える。 以下のように関数を定義する。
code:tsx
const XhtmlLink = ({ children, ...props }: any) => {
return React.createElement("xhtml:link", props, children);
};
以下が使用例。
code:ts
以下の<urlset>のように{...{"xmlns:xhtml": "http://www.w3.org/1999/xhtml"}}を使うことができる。ほぼイディオムかすると思われる。
code:typescript
JSX(ReactElement型)を最終的に文字列として出力したくなる。 その時にはimport {renderToString} from 'react-dom/server';のrenderToStringを使えば良い。
以下がシンプルな使用例。
code:tsx
renderToString(<h1>hello, world</h1>);
実際はXMLにしたかったので<?xml version...>を上部につけるために以下の関数を定義して内部でrenderTtoString()を呼び出すようにしている。ここは局所的なので文字列の足し算を使っている。 code:tsx
function renderXmlToString(xml: ReactElement): string {
return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + renderToString(xml);
}
import constants from '../src/constants';のようにすればsrc/で定義されているTypeScriptのファイルもインポートできる。 実際のコミット
以下が実際にこのsitemap.xmlを生成するためのコミット。より詳細を確認できるはず。
リンク先のscripts/generate-sitemap.tsxがTSXを使ってsitemap.xmlを生成するためプログラム。