freeeの開発情報ポータルサイト

Ruby のコードから TypeScript のコードを生成する

この記事は freee Developers Advent Calendar 2024 の 16 日目の記事です.

freee でエンジニア兼 Dev Branding をやっているけむりだま (@_kemuridama) です. 今年は Apple Vision Pro を購入したのですが, 予約したことを X に投稿したらフジテレビに開封時の取材を申し込まれるというレアなイベントが発生しました. 先日公開された visionOS 2.2 から導入された Mac仮想ディスプレイ機能のウルトラワイド対応, 家で開発してるときにとても便利なので会社でも使えないかなーと願っています. (社用 PC はセキュリティの都合で AirPlay が禁止されているため Mac仮想ディスプレイ機能を使うことができない……)

今回は私が普段エンジニアとして開発を行っている「freee会計」というサービスで Ruby で書かれているバックエンドのコードを元にフロントエンドのコードを生成する話を書いていこうと思います.

バックエンドとフロントエンドのデータの受け渡し

freee会計はバックエンドを Ruby on Rails (Ruby), フロントエンドを React (TypeScript/JavaScript + Flow) という構成で開発されています. バックエンドとフロントエンドは疎結合になるような構成を目指していて, 基本的にフロントエンドからのデータ取得は API 経由で行われます. しかし歴史的な都合から一部のデータは API 経由ではなくバックエンドで View となる erb に直接 JavaScript のコードを記述し, グローバル変数を介してフロントエンドにデータの受け渡しを行っている部分があります.

この仕組みはコードの変更ファイル数が多くなる傾向があります.

  1. バックエンドでデータ取得の helper を実装する
  2. erb で helper を使って JavaScript としてデータを埋め込む
  3. TypeScript のコードと Flow の型定義を実装する
  4. フロントエンドで埋め込まれたデータを使うロジックを実装する

また, 単純に変更ファイル数が多くなるのも課題ですが, すべて人間が手動で変更する必要があるため, typo などの記述ミスやバックエンドとフロントエンドの型の整合性が壊れるなど, 他にも問題をはらんでいます.

これらの問題を解決するため, バックエンドのコードを元にフロントエンドのコードを自動生成する仕組みを導入しようと考えました.

Prism Ruby Parser と TypeScript Compiler API

Prism Ruby Parser は Ruby 3.3 から導入された新しい Ruby parser です. 既存の parser に対してエラートレラントになったという点が 1 番の目玉機能でしたが, ポータビリティやメンテナビリティなども重要視して開発されています. また Prism は WebAssembly を提供しており Node.js から利用するための @ruby/prism という package も公式で提供されています.

Prism は parser なので Ruby のソースコードを解釈し AST (Abstracut Syntax Tree, 抽象構文木) を作成します. この作成された AST を元にフロントエンドのコード生成に使用します.

TypeScript Compiler API は TypeScript の compiler をプログラムから使うための API です. Prism 同様に TypeScript のコードを parse する機能や AST をベースに TypeScript のコードを生成する機能などを持っています.

Ruby のコードから TypeScript のコードの生成

前述した 2 つのツールを駆使して次のような流れでバックエンドである Ruby のコードからフロントエンドの TypeScript のコードを生成します. Prism が Node.js から扱えることもあり, これらの処理はすべて TypeScript で実装しました.

  1. Ruby のソースコードから Prism を使って Ruby の AST を取得する
  2. Ruby の AST を TypeScript Compiler API を使って TypeScript の AST に変換する
  3. TypeScript の AST から TypeScript のソースコードと Flow の型定義を生成する
  4. GitHub Actions (CI) で Pull Request ごとに生成されたコードの整合性をチェックする

型情報などのコードを手動で書く必要がなくなったため, 実際に実装が必要なのはバックエンドでデータ取得ための helper を実装する部分とフロントエンドで埋め込まれたデータを使う部分の 2 ヶ所のみになりました.

最後に

この仕組みにより Ruby のコードを書くだけで TypeScript のコードを得ることができるようになり, 煩雑なコードを人間が手動で書く必要がなくなりました. また, バックエンドとフロントエンドのデータの整合性を担保しやすくなりました. 社内でもこの仕組みによって実装が楽になったなどの声をもらい, freee会計の開発をしているエンジニアの生産性を少しは向上できたんじゃないかなと思います.

Ruby には基本的に型がないため, 今のところ Feature Flag などある程度型が自明のものにしかこの仕組みは適用できていません. freee会計に導入されている Sorbet という Ruby 用の型チェッカーの情報を使ってこの仕組みを改良できないかなと思っています. (そもそも erb 経由のデータの受け渡しをやめたい……)

話は変わりますが, 明後日 12/18 (水) 18:00 から学生向けに React チュートリアルのハンズオンイベントを開催する予定です. 私も講師として参加するので, 興味のある学生さんはぜひお越しください!

freee.connpass.com

明日の freee Developers Advent Calendar 2024 は Dev Branding の活動を一緒にやっている WaTTson の記事が投稿される予定です. お楽しみに!!