Zenn
📱

Lynxを動かす:新世代クロスプラットフォーム開発ツールの第一印象

2025/03/07に公開
6

先日ByteDanceから新しいReactベースのマルチプラットフォーム開発ツールのLynxが発表されました。

https://lynxjs.org/

仕組み的にも思いっきり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 };
    }
  }
}

https://lynxjs.org/guide/scripting-runtime.html#javascript-runtime-1

<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なのか気になりました。

https://lynxjs.org/guide/ui/elements-components.html#behind-the-elements-native-rendering

Behind the Elements: Native Rendering

ドキュメントを見る限り後者のReact Nativeパターンのようです。ということはCSSのサブセットをネイティブUIに表示するための独自スタイリングエンジンを内部に持っているのですね。凄すぎます。

CSSをどうやって処理してる?

そうなると気になるのはCSSの処理をどうやっているのか、WebViewを使っていないということはCSSの解釈を独自でどこかでやっているはずです。

https://github.com/lynx-family/lynx/tree/develop/core/renderer/css

この辺りにCSSに関するコードがあったから独自実装しているのかもしれません。コードちょっと読んでみた程度ではわからなかったので詳しいことはおいおい調べます。

https://lynxjs.org/guide/ui/layout.html#understanding-layout

ドキュメントを見る限りWebとはやはりルールが違っていて全てのElementがblock-levelだと言っています。でもかなりのCSSプロパティがサポートされているので、もしかしてpanda-cssとかのCSS in JSツールが動かせるかも..?

https://lynxjs.org/api/css/properties.html

ちなみに親のviewに試しにcolorプロパティをつけてみましたが子のtextの色は変わらなかったのでCSS inheritanceはなさそうです。

Native APIにどうやってアクセスする?

これもめっちゃ大事ですね。NativeのAPIにアクセスするためにビルドが必要になったりライブラリのラッピングがいけてないと初期のReact Nativeの二の舞になってしまいます。

https://lynxjs.org/guide/use-native-modules.html#platform=ios

ドキュメントを見るとNative ModulesというネイティブとJSの橋渡しをする仕組みがあるらしいです。とりあえず使えないことはなさそうです。

ただガイドにはLynx Explorerに自前のネイティブコードを入れた状態でビルドしてその上で対応するJSを動かすコードを書けと言われています。

これだとネイティブ側に当該実装があったりなかったりするしビルドも複雑になりそうです。というかネイティブモジュールを使うライブラリの配布はどうするのでしょうか。

この辺はExpoのCNGが一歩先を行っていると思います。

https://zenn.dev/woodstock_tech/articles/293a5c1d062ec6

所感

スタイリングがとにかくすごいです。CSSとの互換性を(おそらく)独自ツールでここまで持たせているのに感動しました。RspackベースのビルドシステムでJSのビルドも早そうだし期待が持てます。

一方でネイティブコードとのやりとりはあまり練られていなさそうでちょっと残念です。レンダリングのアイデアが先にあってネイティブとの連携はとって付けた感があります。ここは今後の発展に期待したいところです。

現状だと枯れていなさすぎてさすがに本番採用は難しいですが、ByteDanceというクソデカ資本がバックについているのでこのまま発展していってほしいです!楽しみです。

6

Discussion

ログインするとコメントできます