BiwaSchemeでReactNativeする

というわけで、BiwaSchemeでReactNativeできるようになった。

左はMS Edgeで動作するreact-native-domで、右はWindows版のReactNative( https://github.com/Microsoft/react-native-windows )となっている。(React Nativeの方に出ている"000 000"はFPSカウンタ) ...どちらもChakraなので後でAndroidiOS(JavaScriptCore)でも試すことにする。
もっとも、まだViewとTextしか動作を確認していない https://github.com/okuoku/biwasyuni-editortest/blob/2342b33e818b7c0689208f958432b427e9c2381a/yunilib/appmain-rn.sls#L42 。通常のReactとコードを共有する上でどういう中間ライブラリを挟むのが良いのか考え中。。

"use strict"に対応する

React Nativeでは、標準のJavaScript bundlerとしてMetro( https://facebook.github.io/metro/ )を採用している。が、このMetroはJavaScriptのパースに直接Babelを使用しているため、非strictなコードを読むことができない。Babelは何もオプションを指定しないとソースコードをES6だと思ってパースすることになるが、Metro側にパーサのオプションを設定する方法がない。
通常のシチュエーションではあまり問題にはならないが、BiwaSchemeはライブラリ実装のためにstrictモードでは禁止されているwithを使用している( https://github.com/biwascheme/biwascheme/blob/2f69a3dfab57f8ec9b5c2c98702db703192bf8b7/src/library/extra_lib.js#L2 )ため、ReactNativeで直接使うことができない。
...どうしようか悩ましいところだが、今回はforkで解決することにした。つまり、withによってトップレベルにインポートすることが期待されるシンボルを全部ローカルスコープに導入してしまう( https://github.com/okuoku/biwascore/blob/d9ca63989332c758c3c7fde9b69898539aebe6bc/gen/r6rs_lib.js#L10 )ことで、withが無くても元のコードを直接実行できるようにした。
また、ついでに各拡張ライブラリはCommonJSのmoduleとして切り出している。これにより、必要なライブラリだけをアプリケーションに組込めるようになった。upstreamとのやりとりが簡単になるように、ファイル構成はそのままにしている。

未解決の問題

まだ色々と未解決の問題が残っている。まぁ本命はWebアプリ、というか実際のアプリをPWAで作るつもりなのでReactNativeは一旦置いておく。。
アセットをJS側にロードするためのプラットフォーム共通手法が無い。ReactNativeではアセットはコンポーネントレンダリングされるものという想定で、Imageのようなアセットをレンダリングするための(組込みの)コンポーネントに渡すことしかできない。今回の場合はSchemeソースコードコンポーネントではなくJavaScript側に渡したいが、ReactNativeのアセットシステムではURLしか基本的に取れない。このためHTTPサーバを前提にできるデバッグ実行時でしか動作しない -- ただしreact-native-domではそもそもブラウザで動作しているのでアセットは常にfetch() APIで取得できる。react-native-windowsの場合は、パッケージをビルドするとアセットのURLがHTTPではなく"ms-appx://"のURLになるためfetch() APIで処理させることはできない。
ロジックを外部に持っていきたい場合はTransformerとして書くことになるが、これも基本的にJavaScript言語へのコンパイルを想定しているので今回のユースケースには合わない。たとえばS式をJSONに変換するだけのTransformerを用意して実行すれば良さそうでは有るが。。
(ちなみに、本当にマジでblobをサポートする必要があるならrn-fetch-blobが有る https://github.com/joltup/rn-fetch-blob これはios/androidのために専用のプラットフォームサポートを持っている。)
ロジックをプラットフォーム毎に切り分ける方法が無い。ReactNativeはソースコードやアセットを(R6RS処理系で見られるような)拡張子ルールで切り分ける方法を採用している(hoge.windows.js を代わりに読む)。今のところyuniではプレフィックスルールを採用しているため、まるでエンディアン論争のように逆になってしまっている。yuniはそもそも対応処理系が多いのでプレフィックスルールにしないと大量のファイルを撒くことになる -- それよりは、移植層を環境毎のディレクトリに纏めた方が自然だと考えている、が、(R6RSも含め)世間的には逆というわけでちょっとどうしようか悩むところ。。
また、ReactNative自体も複雑なプラットフォーム解決ができないという問題がある(MSのプロポーサル https://github.com/facebook/metro/issues/135 )。つまり、ios/android/windowsのような1段階目の派生のみがビルドシステムではサポートされており、windowsが本来持つであろうxboxとかwpfのような派生を取り入れる余地がない。
プラットフォームの切り分けは本来:

  1. ビルド時の切り分け。React-nativeの場合最終的なパッケージはプラットフォーム間で共用されないためこちらを採用できる。
  2. 動作時の切り分け。Webで動作する場合、例えばテスト/本番とかモバイル版/デスクトップ版のような実行時の切り分けも発生する可能性がある。

のようなバリエーションが有るため仕様含めちょっと真面目に考える必要がある。。
yuniの重要なデザインチョイスはScheme界隈では比較的一般的なcond-expandを捨てたことで、ライブラリ名の差し替えのみのサポートとした。これにより環境毎のバリエーションの出方を固定することにしている。これをやり切れるかどうかはちょっとなんとも言えないが。。