Webとネイティブを融合させてRemixのようなデータ取得ができるフレームワーク”One”と、RSCの未来
Tamaguiチームから、Webとネイティブアプリを単一コードベースで開発するReactフレームワーク「One」が発表されました。
近年ではWebとネイティブを単一ソースコードで開発するスタックのことはUniversal Appと呼ばれ、徐々に注目を集めています。例えばユニバーサルなスタイルフレームワークであるTamaguiは、ExpoとMetro、Next.jsを使ったボイラープレートを提供してきました。
OneはReact NativeのビルドツールであるMetroの代わりに、Viteプラグラインとして動作します。また、WebサーバーとしてHonoを採用しています。
ドキュメントを一通り読んでみて、これはネイティブアプリにおけるReact Server Components(以下RSC)実装の未来を示したフレームワークではないかと思ったので、紹介&考察記事を書こうと思いました。
Oneは何を解決しようとしているのか
OneはWebとネイティブアプリの両方を単一コードベースでの開発できるようにした上で、さらにWebではSPA, SSR, SSGをサポートすることで、軽量で高速なユーザー体験を提供しようとしています。
内部的には、Metroの代わりに開発した、vxrnというReact Nativeアプリをホットリロードで提供するViteプラグインツールがベースとなっています。
Oneの理念として、Railsをリスペクトしつつも、現代においてオールインワンなソリューションは求められていないため、そのような姿は目指さないとしています。一方で、サードパーティライブラリとして取り外し(eject)可能なデータライブラリを組み込むことで、開発生産性を高めたいと考えているようです。
ルーティング
OneにはWebとネイティブに両対応した、ユニバーサルなルーティングシステムがあります。
├── _layout.tsx: このディレクトリ以下のすべてのファイルをラップする
├── index.tsx: "/" にマッチ
└── blog.tsx/
├── index.tsx: "/blog" にマッチ
├── [slug].tsx: "/blog/hello" のように、サブパスにマッチ
└── [...rest].tsx: "blog/hello/world" のように、すべてのサブパスにマッチ
Next.jsと似た、ファイルベースのルーティング設定になっています。 _layout.tsx
ファイルは特殊なファイルで、ページをラップしたコンポーネントを作れます。
このルーティングシステムは、Expo Routerをフォークして作られており、内部にかなり手を入れているようです。
RemixライクなLoader
app
ディレクトリの中のルートに限定して、サーバーからクライアントへデータを一度だけロードするのに便利なLoader機能が使えます。これはRemixで提供しているものと同じようなものですが、特徴的なのはネイティブアプリからもLoaderが利用できるということです。
import { useLoader } from 'one'
export async function loader() {
return {
user: 'tamagui'
}
}
export default function HomePage() {
const data = useLoader(loader)
return (
<p>
{data.user}
</p>
)
}
Loaderで取得したデータにアクセスするためにはuseLoaderを使わなければなりません。useLoader hookは型安全なデータを返します。
Loaderとそれに付随するimportは、クライアントバンドルからは削除されるため、秘匿情報を扱うことができます。つまり、サーバーサイドで実行されるため、DBクライアントを直接操作することもできます。
これを実現するために、ネイティブアプリでもWebフロントエンドサーバーがデプロイされているURLを環境変数にセットする必要があります。
これは、最近のフロントエンドフレームワークと同じように、サーバーサイドの役割を吸収することで、GraphQLのようなインターフェースを介さなくても自由度が高い状態でデータを取得する流れに沿っているなと思いました。この辺の話はmizchiさんが最近出されたスライドがとてもわかりやすかったのでおすすめです。
SPA / SSR / SSG
vite.config.ts に記述することで、Webページの動作モードについて設定ができます。
import { one } from 'one/vite'
export default {
plugins: [
one({
web: {
defaultRenderMode: 'spa'
}
})
]
}
それだけでなく、ルート内の個々のページごとに動作モードを変更することもできます。
api
, spa
, ssr
, ssg
モードがサポートされており、ファイル名にサフィックスをつけることで動作を切り替えできるようです。
-
route+ssg.tsx
- SSG route -
route+spa.tsx
- SPA route -
route+ssr.tsx
- SSR route -
route+api.tsx
- API route
ローカルファーストなデータ取得方法: Zero
まだ利用できませんが、サードパーティライブラリとして、年末にはローカルファーストなデータ取得方法としてZeroをサポートするとのことです。Zeroを使わないという選択肢も可能のようで、現状のボイラープレートではdrizzleが使われていました。
ZeroはWeb用の汎用同期エンジンとして、既存のデータベースの前段に置くことで、ローカルからデータベース全体の任意のクエリを実行できるシステムだとされています。ローカルのクライアントには、頻繁に利用される100MBのデータをキャッシュしており、自動的にキャッシュが利用されるという仕組みのようです。
ネイティブアプリでデータを取り扱うときは、ローカルファーストな手法が必要になってきます。その分野には、React Query, Firebase, Meteor, RethinkDB, PouchDB, gqty, TinyBaseなど、本当にたくさんの競合がいます。
その中でもZeroを選ぶ特徴として以下が書かれています。が、まだZeroはリリースされていないので、どこまで実用的なのかはリリースを待って確認するしかありません。
- 小さなクライアントバンドルサイズ
- 最初にクエリしたものだけをフェッチするスマートな同期
- joinのようなサブクエリ
- Optimistic mutations
- 自動ローカルストレージとストレージ管理
- サーバーとのリアルタイム同期
- 複雑なデータ競合処理や、データ構造/mutationに関する制限的なルールは不要
- PostgreSQL上で動作
- 導入が簡単
- 大規模データセットにも対応
考察: ネイティブアプリとRSCの未来について
OneはRSCについて、今はサポートせず、将来的には限られたサブセットのみサポートするとしています。ローカルファーストなデータ取得・操作方法であるZeroを導入することで、データ取得と操作が単純化されるため、必要ないと考えているようです。
彼らが言うに、ネイティブアプリにおけるRSCの採用で望ましくない理由は、クライアントとサーバーのDOMツリーが混在することで、"function coloring” とに似た問題が発生するためだと主張しています。つまり、一箇所でサーバーファーストな機能を入れてしまうと、アプリ全体もサーバーファーストになってしまう(汚染されてしまう)問題があるということです。この問題については、将来的にはRSCのサブセットを導入することで解決したいと考えているようです。
個人的にこの意見はすごく納得できる考え方です。もしRSCそのものを入れる代わりに、オフラインの時はアプリが使えない状態でもいいじゃん!という割り切りが必要になった場合、それが最終的に行き着くところはWebViewによる表示でいいじゃん、という着地点になると思うからです。(現実にそうしているアプリはたくさんありますし、悪い設計というわけではないと思います。私たちのアプリもかなり多くの部分でWebView表示を採用しています。)
一方で、ネイティブアプリをわざわざユーザーに提供する理由として、例えばLinearやNotion、スマートニュースのようなアプリを作る時に、滑らかなアニメーションや操作体験だけでなく、例えば地下鉄で少しの間だけネットワーク通信ができない状態になったとしても使い続けられるという点が挙げられると思います。もちろん、ホーム画面に置いてもらってアクセス性をよくしたり、プッシュ通知を送れるようにするというのは大前提です。
Oneはサーバーのラウンドトリップを回避し、サーバーとクライアントの間でメンタルモデルを分割することで、より良いユーザー体験を提供しようとしています。また、良いユーザー体験を探求するためにWebとネイティブアプリ両方提供しようとすると、大きなチーム構成にならざるを得ません。そういった状況を避けるのために、Webとネイティブを一つのコードで同時に開発するのは、小さいチームでは特に役に立ちます。
ExpoとNext.jsを使い単一コードベースで開発を行った経験のある私の感覚では、多少の癖はあるものの、ユニバーサルスタックは現実的に使えるアーキテクチャだと思っています。ですので、それを進化させようとしているOneには期待しかありません。
Oneの正式なリリースは年末ごろになると発表されています。
Xやっています → https://x.com/jaqk_and
Discussion