Lynxを動かす:新世代クロスプラットフォーム開発ツールの第一印象
先日ByteDanceから新しいReactベースのマルチプラットフォーム開発ツールのLynxが発表されました。
仕組み的にも思いっきりReact Nativeとの競合になりそうですが、何はともあれ動かしてみないと何もわからないので、サンプルプロジェクトを動かしてみた備忘録と気になったことを調べたログをまとめました。
Quick startを試す
additional toolsのインストールでbiomeが選択肢に出てきてモダンだと思いました。
❯ bun create rspeedy@latest
◆ Create Rspeedy Project
│
◇ Project name or path
│ rspeedy-project
│
◇ Select language
│ TypeScript
│
◇ Select additional tools (Use <space> to select, <enter> to continue)
│ none
│
◇ Next steps ─────────────╮
│ │
│ 1. cd rspeedy-project │
│ 2. git init (optional) │
│ 3. bun install │
│ 4. bun run dev │
│ │
├──────────────────────────╯
│
└ All set, happy coding!
デバッグする端末やシミュレーターにLynx Explorerというアプリをインストールしてその上でアプリを動かす方式のようです。ここは思いっきりExpoGoと同じですね。ただしExpoGoは自動インストールしてくれるのに対してこちらはアプリのバイナリをダウンロードしてシミュレーターにドラッグ&ドロップでインストールする必要がありました。手間ではないですが小慣れてない印象です。
bun dev
devコマンドで開発サーバーが立ち上がります。Expoと同様にここでは開発サーバーの立ち上げのみをしているようで一瞬でReadyになります。Lynx Explorerで読み込むためのQRコードも表示されます。これをLynx Explorerで読み込むとアプリが実行されます。コードの書き換えると自動的に変更がApplyされます。反映は早いし開発しやすそうです。
import { useCallback, useEffect, useState } from '@lynx-js/react'
import './App.css'
import arrow from './assets/arrow.png'
import lynxLogo from './assets/lynx-logo.png'
import reactLynxLogo from './assets/react-logo.png'
export function App() {
const [alterLogo, setAlterLogo] = useState(false)
useEffect(() => {
console.info('Hello, ReactLynx')
}, [])
const onTap = useCallback(() => {
'background only'
setAlterLogo(!alterLogo)
}, [alterLogo])
return (
<view>
<view className='Background' />
<view className='App'>
<view className='Banner'>
<view className='Logo' bindtap={onTap}>
{alterLogo
? <image src={reactLynxLogo} className='Logo--react' />
: <image src={lynxLogo} className='Logo--lynx' />}
</view>
<text className='Title'>React</text>
<text className='Subtitle'>on Lynx</text>
</view>
<view className='Content'>
<image src={arrow} className='Arrow' />
<text className='Description'>Tap the logo and have fun!</text>
<text className='Hint'>
Edit<text style={{ fontStyle: 'italic' }}>{' src/App.tsx '}</text>
to see updates!
</text>
</view>
<view style={{ flex: 1 }}></view>
</view>
</view>
)
}
初期状態のコードはこんな感じです。viewやtextというReact ElementとCSSを直接読み込んでいるっぽいのが気になります。
.Background {
position: fixed;
background: radial-gradient(
71.43% 62.3% at 46.43% 36.43%,
rgba(18, 229, 229, 0) 15%,
rgba(239, 155, 255, 0.3) 56.35%,
#ff6448 100%
);
box-shadow: 0px 12.93px 28.74px 0px #ffd28db2 inset;
border-radius: 50%;
width: 200vw;
height: 200vw;
top: -60vw;
left: -14.27vw;
transform: rotate(15.25deg);
}
読み込んでいるCSSの一部はこんな感じです。radial-gradientのような複雑なCSSも動いているのでWebとの互換性は高そうに見えます。
declare module 'react' {
namespace JSX {
// Should copy from above IntrinsicElements
interface IntrinsicElements {
'component': ComponentProps;
'filter-image': FilterImageProps;
'image': ImageProps;
'inline-image': ImageProps;
'inline-text': TextProps;
'inline-truncation': NoProps;
'list': ListProps;
'list-item': ListItemProps;
'list-row': ListRowProps;
'page': PageProps;
'scroll-view': ScrollViewProps;
'text': TextProps;
'view': ViewProps;
'raw-text': StandardProps & { text: number | string };
}
}
}
<view />
とか<text/>
とかはLynx独自のReact要素のようです。型定義を見ると基本の要素はview、text、image、list、scroll-view、pageとその派生くらいのようです。
気になるところ
JSはどうしてる?
PrimJSという独自のJSエンジンを積んでいるようです。QuickJS派生らしいので完全独自実装ではないけど最適化とかしたかったのかなと思います。
Dual Thread Architectureというのを採用していてJSがMainとBackgroundの2つで動くらしいです。AndroidはどちらもPrimJSが動くらしいですが、iOSのプロダクションビルドのみBackgroundがJavaScriptCoreになるそうです。
レンダリングはどうしてる?
ionicみたいにWebViewにレンダリングを任せているのか、Flutterみたいに完全独自レンダリングなのか、ReactNativeみたいにスタイリングだけ独自でやって描画はネイティブViewなのか気になりました。
ドキュメントを見る限り後者のReact Nativeパターンのようです。ということはCSSのサブセットをネイティブUIに表示するための独自スタイリングエンジンを内部に持っているのですね。凄すぎます。
CSSをどうやって処理してる?
そうなると気になるのはCSSの処理をどうやっているのか、WebViewを使っていないということはCSSの解釈を独自でどこかでやっているはずです。
この辺りにCSSに関するコードがあったから独自実装しているのかもしれません。コードちょっと読んでみた程度ではわからなかったので詳しいことはおいおい調べます。
ドキュメントを見る限りWebとはやはりルールが違っていて全てのElementがblock-levelだと言っています。でもかなりのCSSプロパティがサポートされているので、もしかしてpanda-cssとかのCSS in JSツールが動かせるかも..?
ちなみに親のviewに試しにcolorプロパティをつけてみましたが子のtextの色は変わらなかったのでCSS inheritanceはなさそうです。
Native APIにどうやってアクセスする?
これもめっちゃ大事ですね。NativeのAPIにアクセスするためにビルドが必要になったりライブラリのラッピングがいけてないと初期のReact Nativeの二の舞になってしまいます。
ドキュメントを見るとNative ModulesというネイティブとJSの橋渡しをする仕組みがあるらしいです。とりあえず使えないことはなさそうです。
ただガイドにはLynx Explorerに自前のネイティブコードを入れた状態でビルドしてその上で対応するJSを動かすコードを書けと言われています。
これだとネイティブ側に当該実装があったりなかったりするしビルドも複雑になりそうです。というかネイティブモジュールを使うライブラリの配布はどうするのでしょうか。
この辺はExpoのCNGが一歩先を行っていると思います。
所感
スタイリングがとにかくすごいです。CSSとの互換性を(おそらく)独自ツールでここまで持たせているのに感動しました。RspackベースのビルドシステムでJSのビルドも早そうだし期待が持てます。
一方でネイティブコードとのやりとりはあまり練られていなさそうでちょっと残念です。レンダリングのアイデアが先にあってネイティブとの連携はとって付けた感があります。ここは今後の発展に期待したいところです。
現状だと枯れていなさすぎてさすがに本番採用は難しいですが、ByteDanceというクソデカ資本がバックについているのでこのまま発展していってほしいです!楽しみです。
Discussion