Help us understand the problem. What is going on with this article?

TypeScriptで書いたプログラムをPython等で動かす魔法の「jsii」を紹介

まず結論から書くと、下記の図のような事ができます。

image.png

上側に表示しているTypeScriptで書いたプログラムを、下側のPythonプログラムから実行している例です。

これは、jsiiというライブラリを使って、TypeScriptプログラムをPythonで実行可能なライブラリに変換しています公式ドキュメントにthe magic happensと書いてあるとおり、魔法のような技術です。

jsiiはPythonの他にもJavaなどの言語で利用できますが、本稿ではTypeScriptで書いたプログラムをPythonで実行します。

実際にやってみましょう。

本稿の実行環境

  • TypeScript 3.6.4
  • Python 3.7.5
  • Node.js 13.1.0

jsiiを使ってみる

jsiiは大まかに、下記のような流れで利用できます。

  1. TypeScriptでプログラムを記述
  2. jsiiを使って他言語のパッケージをビルド
  3. 他言語からインストールして利用する

TypeScriptプログラムの記述

サンプルとして、TypeScriptで適当なFizzBuzzのプログラムをlib/index.tsに記述します。

lib/index.ts
export class MyClass {
  fizzBuzz(arg: number): string {
    if (arg % (3 * 5) === 0) {
      return 'FizzBuzz';
    } else if (arg % 3 === 0) {
      return 'Fizz';
    } else if (arg % 5 === 0) {
      return 'Buzz';
    }
    return arg.toString();
  }
}

もしTypeScriptとして普通に実行することで動作確認したい場合は、$ npx ts-node lib/index.tsなどお好みの方法で実行します。

package.jsonの記述

jsiiはビルド時にpackage.jsonの情報を使って、各言語のライブラリ用のメタ情報を登録していきます。そのため、ある程度package.jsonを「ちゃんと」書いていないとjsiiを動作させることはできません。

下記が設定例です。

package.json
{
  "name": "jsii-example",
  "version": "1.0.0",
  "main": "lib/index.js",
  "types": "lib/index.d.ts",
  "author": "your_name",
  "repository": {
    "url": "https://example.com",
    "type": "Git"
  },
  "license": "MIT",
  "jsii": {
    "outdir": "dist",
    "targets": {
      "python": {
        "distName": "your_name.jsii_example",
        "module": "your_name.jsii_example"
      }
    }
  },
  "devDependencies": {
    "jsii": "^0.20.6",
    "jsii-pacmak": "^0.20.6"
  }
}

上記のようにrepositoryauthorなど普段省略しがちな箇所が、jsii利用時には必須になります。またmaintypesには、lib/index.tsと同じディレクトリで指定する必要があります。

加えてjsii固有の設定として、jsiiという所に各言語のパッケージ用の情報を記述します。今回はPythonのパッケージをビルドするため、targetspythonとしてモジュール名の情報を記述しています。

jsiiでのビルド

jsiiのビルド実行に必要なパッケージをインストールします。

$ npm install --save-dev jsii jsii-pacmak
$ echo "jsii
wheel
publication" > requirements.txt
$ pip install --require requirements.txt

上記インストールが完了したらjsiiコマンドでビルドし、jsii-pacmakコマンドでパッケージングします。

$ npx jsii
$ npx jsii-pacmak

dist/pythonディレクトリに、your_name.jsii_example-1.0.0.tar.gzが作成されていたら成功です。

Pythonから実行する

先程作成したパッケージファイルを、Pythonライブラリとしてインストールします。

$ pip install dist/python/your_name.jsii_example-1.0.0.tar.gz

このライブラリをimportして、先程のfizzBuzz関数を呼び出すPythonプログラムを記述します。

python.py
import your_name.jsii_example

obj = your_name.jsii_example.MyClass()

print(obj.fizz_buzz(2))
print(obj.fizz_buzz(3))
print(obj.fizz_buzz(5))
print(obj.fizz_buzz(15))

いよいよ実行します。

$ python python.py 
2
Fizz
Buzz
FizzBuzz

TypeScriptで書いたコードを、Pythonから呼び出すことができました!

Dockerでjsiiを使う

ここまで述べてきた実行環境構築は複数言語のインストールが必要だったりと少々面倒のため、今回jsii実行に必要なPythonとNode.jsをまとめて用意できるDockerfileを用意しました。

FROM python:3.7.5-slim

ENV DIR /app
WORKDIR $DIR
RUN apt update && apt install -y nodejs npm

# setup python
COPY requirements.txt $DIR
RUN pip install --requirement requirements.txt

# setup node
COPY package.json $DIR
RUN npm install

# main: lib/index.ts
COPY lib/ $DIR/lib

先述のpackage.json,requirements.txt,lib/index.tsを用意した後、下記のようにして使います。

$ mkdir dist
$ docker build --tag jsii-example .
$ docker run --rm --volume $(pwd)/dist:/app/dist jsii-example /bin/bash -c 'npx jsii && npx jsii-pacmak'

dist/以下にパッケージファイルが生成されたら成功です。

jsiiの仕組み

jsiiの仕組みについては、jsiiのドキュメント内に解説があります。
jsii/runtime-architecture.md at master · aws/jsii

Python等のホストコードがjsii利用箇所に到達した時、nodeの子プロセスを立ち上げてプログラムを実行し、その結果をPythonで受け取っているようです。ホストの/tmpディレクトリを見てみると、プログラムを一時的にnpm installしていると思しき形跡が見つかると思います。

本来Pythonだけで終わる処理がNode.jsとの通信にも依存するようになっているため、パフォーマンスは犠牲になっている点がドキュメント内でも述べられています。jsiiは本番環境などパフォーマンスが要求されるアプリケーション内での動作は難しく、開発やビルド用ツール等での利用が想定されています。

jsiiの利用例

jsiiはAWSが開発しているOSSです。実際の使用例として、AWSの提供するInfrastructure as CodeのツールであるAWS CDK内でjsiiが使われています。aws-cdkはjsiiと同じく、TypeScript、Python、.NET、Javaでインフラ構成を記述できるようになっています。

AWS クラウド開発のよくある質問 より引用

AWS は、AWS Construct ライブラリパッケージのビジネスロジックを TypeScript で構築し、サポートされている各プログラミング言語へのマッピングを提供します。これにより、AWS CDK コンストラクトの動作が異なる言語間で一貫していることを確認でき、すべての言語で利用できる包括的なコンストラクトパッケージのセットを提供できます。AWS CDK プロジェクトで作成したコードはすべてお客様ご希望のプログラミング言語でネイティブになっています。JavaScript ランタイムはお客様のプログラミング経験の実施詳細です。jsii プロジェクトは https://github.com/aws/jsii で参照できます。

Tips

  • jsiiで実行できるTypeScriptには制限がある
    • 分かりやすい例を挙げると、他言語の予約語をTypeScriptで使用しようとすると正常に動作しない場合があります。
    • 他、どういった制限があるかはドキュメントに記載されています。
  • Python 3.8には未対応
  • Ruby対応は開発中
    • ドキュメントにハッキリとは書いていないのですが、Historyやソースコード内を読んでいくとRubyに対応しているかのような記述が所々に見つかります。
    • package.json内のtargetsrubyと指定すると、ちゃんとjsiiは動作してdist/ruby/にパッケージらしきファイルが生成されるのですが、TypeScriptのソースコードをそのまま含めるプログラムが動いているだけのようでgemの生成は実装されていません。
    • Ruby対応についてはissueが立っています

まとめ

jsiiを使って、TypeScriptで書いたプログラムをPythonのソースコード内で実行することができました。まだ開発中の部分もあり制約は多いものの、実際にAWS CDKで使われているため一定の将来性もあります。もし「1つのコードベースで作ったツールを、複数の言語から利用したい」という場合には、このjsiiが選択肢に挙がるのではないかと思います。

今回利用したプログラムはGitHubにも上げています。もし興味を持った方はご覧ください。
https://github.com/s2terminal/jsii-example

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away