ソフトウェアエンジニア必見!lynx-family/lynxによるコード共通化とモノレポ戦略
「またか!」「あのプラットフォームでは動かない!」「リリースが遅れる!」...と、プロジェクトマネージャー(PM)が頭を抱える声が聞こえてきそうです。彼らの苦悩の種、それは「マルチプラットフォーム開発の複雑さ」です。
PM
「A君、Web版の機能、最高だよ!次はモバイルアプリにも展開してくれ!」
エンジニアA
「了解です!...って、モバイルはReact Nativeで、WebはReact。コードの共通化が難しくて、また一から書き直す部分が多いです...。テストもプラットフォームごとに必要で、時間が...。」
PM
「え、じゃあ、あのデスクトップアプリはどうなってるんだ!?」
エンジニアB
「デスクトップはElectronですが、UIのロジックがWebと違ってて、これもまた...。機能のバグ修正が、すべてのプラットフォームで別々に発生してます...。」
PM
「うわあああ!リリース日は迫ってるのに、全部のプラットフォームを同時にアップデートできない!もう嫌だー!」
この苦悩を解消し、エンジニアがPMに「問題ありません、一元管理できます!」と言えるようになるのが、lynx-family/lynx(以下、lynx)が目指す世界です。
lynxは、一言で言うと、「共通のコードベースで、Web、モバイル、デスクトップなど、様々なプラットフォーム向けアプリケーションを構築するクロスプラットフォームフレームワーク」です。
コードの共通化と再利用性の最大化
1つの言語・1つのロジック
lynxを使うと、アプリケーションのビジネスロジック(データの処理、状態管理など)をWeb、モバイル、デスクトップのすべてで共通のコードとして記述できます。
「書くのは一度、動かすのはどこでも」
これにより、プラットフォームごとにコードを書き直す手間が激減し、開発速度が格段に向上します。
メンテナンスの効率化
バグ修正が一箇所で済む
「Webで直したバグをモバイルにも反映しなきゃ...」という作業がなくなり、共通ロジックのバグ修正は一度で済みます。PMが頭を抱える「全プラットフォーム同時リリース」のハードルが下がります。
スキルセットの統一
新しい技術習得の負担軽減
エンジニアは特定の技術セット(例
JavaScript/TypeScriptなど)に集中でき、プラットフォームごとの専門知識(例
ネイティブコード)への依存度を下げることができます。
具体的な導入手順はフレームワークのドキュメントに依りますが、一般的なクロスプラットフォーム開発の観点からイメージを説明します。
まず、lynxプロジェクトを作成します。これは、Webとモバイル(またはデスクトップ)の両方をターゲットにした共通コードを管理するためのモノレポ(Monorepo)構造になることが多いです。
# 独自のCLIツール(例: create-lynx-app)を使用
$ npx create-lynx-app my-universal-app
# プロジェクトフォルダへ移動
$ cd my-universal-app
アプリケーションの核となる部分(例
ユーザー認証、API通信、データモデルなど)を共通コードとして記述します。
// src/common/AuthService.ts
// プラットフォームに依存しない、認証ロジック
export async function login(username, password) {
// APIを呼び出してトークンを取得する処理
const response = await fetch('/api/login', { /* ... */ });
if (response.ok) {
// 状態を保存する
// ...
return true;
}
return false;
}
共通ロジックをインポートしつつ、UIの部分だけを各プラットフォームの技術(Web
React/DOM、モバイル
React Nativeなど)に合わせて記述します。
Web側 (例
Web/LoginPage.tsx)
import { login } from 'common/AuthService'; // 共通ロジックをインポート
const LoginPage = () => {
// ... フォームのHTML/CSS要素をレンダリングする
const handleSubmit = () => {
login(/* ... */).then(/* ... */);
};
// ...
};
モバイル側 (例
Mobile/LoginPage.tsx)
import { login } from 'common/AuthService'; // 共通ロジックをインポート
const LoginPage = () => {
// ... ネイティブのUIコンポーネントをレンダリングする
const handlePress = () => {
login(/* ... */).then(/* ... */);
};
// ...
};
各プラットフォーム向けにアプリケーションをビルドします。
# Webアプリをビルド
$ npm run build:web
# モバイルアプリ(iOS/Android)をビルド
$ npm run build:mobile
「カウンター機能」を例に、ロジックを共通化するイメージを見てみましょう。
状態管理ライブラリ(Recoil, Redux, Zustandなど)を使って、カウントロジックを記述します。
// 共通ロジック:カウント値と、それを操作する関数を定義
let count = 0;
export function getCount() {
return count;
}
export function increment() {
count += 1;
// ★ 重要なのは、この共通関数をWeb/Mobile両方から呼び出すこと
console.log(`New count: ${count}`);
}
export function decrement() {
if (count > 0) {
count -= 1;
}
}
WebのUIコンポーネントから共通ロジックを呼び出します。
import React, { useState } from 'react';
import { getCount, increment, decrement } from '../common/CounterStore';
const WebCounter = () => {
const [currentCount, setCurrentCount] = useState(getCount());
const handleIncrement = () => {
increment(); // 共通のincrement関数を呼び出す
setCurrentCount(getCount());
};
const handleDecrement = () => {
decrement(); // 共通のdecrement関数を呼び出す
setCurrentCount(getCount());
};
return (
<div>
<h1>Web: Current Count: {currentCount}</h1>
<button onClick={handleIncrement}>+</button>
<button onClick={handleDecrement}>-</button>
</div>
);
};
export default WebCounter;
lynxのようなクロスプラットフォームの仕組みは、プロジェクトマネージャーの「あれもこれも!」という要求に応えつつ、エンジニアの「DRY (Don't Repeat Yourself)」の原則を守り、効率的でスケーラブルな開発を実現するための強力な武器になります!