【JavaScript × Terraform】次世代のモダン AltJS「JS.tf」の紹介
人類は HCL (Hashicorp Configuration Language) で JavaScript を記述するべきなので、次世代のモダン AltJS である「JS.tf」をリリースしました。
例えば次のコードは標準出力に hello world
と出力する JS.tf のプログラムです。
data "js_function_call" "hello_world" {
caller = "console"
function = "log"
args = ["hello world"]
}
data "js_program" "main" {
statements = [data.js_function_call.hello_world.statement]
}
# index.js としてファイル出力
resource "local_file" "main" {
filename = "index.js"
content = data.js_program.main.content
}
terraform apply
を実行するとこの HCL が JavaScript としてトランスパイルされます。
$ terraform apply
# index.js が生成される
生成されるのは普通の JavaScript のコードなので、任意の JavaScript ランタイムで実行可能です。
$ node index.js
hello world
この記事では JS.tf の基本的な使い方やメリットについて紹介します。
基本的な使い方
JS.tf の実体は Terraform プロバイダーです。使用するためには required_providers
ブロックに koki-develop/js
を追加する必要があります。
terraform {
required_providers {
js = {
source = "koki-develop/js"
version = "0.11.0" # 任意のバージョン
}
}
}
provider "js" {}
この状態で terraform init
を実行すると JS.tf がインストールされます。
$ terraform init
js_program
JS.tf において最も重要な DataSource は js_program
DataSource です。
js_program
DataSource はステートメントのリストを受け取り、最終的な JavaScript のコードを生成します。
data "js_program" "main" {
statements = [
# ...
]
}
例として、次のプログラムを見てみましょう。
# 定数定義
data "js_const" "example" {
name = "example"
value = 1
}
# 関数呼び出し
data "js_function_call" "log_example" {
caller = "console"
function = "log"
args = [data.js_const.example.id]
}
# プログラム
data "js_program" "main" {
statements = [
data.js_const.example.statement,
data.js_function_call.log_example.statement,
]
}
# 出力
output "source" {
value = data.js_program.main.content
}
順に内容を追っていきます。
まず最初に js_const
DataSource を使用して example
という定数を定義して 1 を代入しています。
# 定数定義
data "js_const" "example" {
name = "example"
value = 1
}
続いて js_function_call
DataSource を使用して console.log
関数を呼び出しています。先ほど定義した example
定数を引数として渡しています。
# 関数呼び出し
data "js_function_call" "log_example" {
caller = "console"
function = "log"
args = [data.js_const.example.id]
}
最後にそれらの DataSource のステートメントを js_program
DataSource に渡すことで、 JavaScript のコードを生成しています。生成された JavaScript のコードは js_program
DataSource の content
から取得できます。
# プログラム
data "js_program" "main" {
statements = [
data.js_const.example.statement,
data.js_function_call.log_example.statement,
]
}
# 出力
output "source" {
value = data.js_program.main.content
}
これが JS.tf を使ってプログラムを記述する基本的な流れです。
上記の例では生成された JavaScript を output
で出力していますが、他にも以下のような活用方法が考えられます。
-
local_file
リソースを使用してファイルに出力する - AWS プロバイダーの
aws_lambda_function
リソースと連携して Lambda 関数を作成する
変数 / 定数の定義
変数 / 定数の定義には js_var
, js_const
, js_let
DataSource を使用します。
data "js_var" "a" {
name = "a" # 変数名
value = 1
}
# => var a = 1
data "js_const" "b" {
name = "b"
value = "hoge"
}
# => const b = "hoge"
data "js_let" "c" {
name = "c"
value = true
}
# => let c = true
変数 / 定数を参照するときは DataSource の id
を使用します。
data "js_const" "example" {
name = "example"
value = 1
}
data "js_function_call" "log_example" {
caller = "console"
function = "log"
args = [data.js_const.example.id] # `example` 定数を参照している
}
# => console.log(example)
演算
演算には js_operation
DataSource を使用します。
data "js_operation" "one_plus_one" {
left = 1
operator = "+"
right = 1
}
# => 1 + 1
data "js_const" "a" {
name = "a"
value = 1
}
data "js_operation" "a_times_2" {
left = data.js_const.a.id
operator = "*"
right = 2
}
# => a * 2
data "js_let" "a" {
name = "a"
value = 1
}
data "js_operation" "a_times_2" {
left = data.js_let.a.id
operator = "*="
right = 2
}
# => a *= 2
関数定義 / 関数呼び出し
js_function
DataSource で関数を定義することができます。
仮引数の定義には js_function_param
DataSource を使用し、戻り値を返す場合は js_return
DataSource を使用します。
# `add` 関数を定義
data "js_function" "add" {
name = "add"
params = [
data.js_function_param.a.id,
data.js_function_param.b.id,
]
body = [data.js_return.a_plus_b.statement]
}
# => function add(a, b) {
# return a + b;
# }
# 仮引数 `a` を定義
data "js_function_param" "a" {
name = "a"
}
# 仮引数 `b` を定義
data "js_function_param" "b" {
name = "b"
}
# a + b
data "js_operation" "a_plus_b" {
left = data.js_function_param.a.id
operator = "+"
right = data.js_function_param.b.id
}
# return a + b
data "js_return" "a_plus_b" {
value = data.js_operation.a_plus_b.expression
}
関数呼び出しには js_function_call
DataSource を使用します。
caller
を指定することでメソッド呼び出しも可能です。
# 先ほど定義した `add` 関数を呼び出す
data "js_function_call" "add" {
function = data.js_function.add.id
args = [1, 2]
}
# => add(1, 2)
data "js_const" "array" {
name = "array"
value = [2, 1, 3]
}
data "js_function_call" "sort" {
caller = data.js_const.array.id
function = "sort"
}
# => array.sort()
その他
その他にも JS.tf では様々な DataSource が用意されています。
-
js_for
,js_while
: for 文 / while 文 -
js_if
: if 文 -
js_increment
,js_decrement
: インクリメント / デクリメント -
js_index
: 添字アクセス - etc...
詳細については公式ドキュメントをご参照ください。
また、 GitHub リポジトリの examples/
ディレクトリにも様々なサンプルコードが置いてありますので、こちらも参考にしてください。
JS.tf を使うメリット
なぜ JS.tf を使うべきなのか、そのメリットについて考えてみます。
JS.tf ではプログラムを全て HCL で記述するため、単純なプログラムを書くだけでもコード量はどうしても多くなってしまいがちです。そのため、「書きやすさ」という点においては他のプログラミング言語と比べて劣っているかもしれません。
しかしプログラムというものは書いてる時間よりも読む時間の方が圧倒的に多いものです。つまり、プログラムは「書きやすさ」よりも「読みやすさ」の方が重要と言ってしまっても決して過言ではありません。チーム開発などで他の人が書いたコードを読むことが多い場合、その重要性はさらに高まります。
その点において、 JS.tf は別に読みやすいということもなく、むしろ単純な処理の流れを追うだけでもソースコード上のあっちこっちに飛び回ることになるので非常に読みにくいです。
さらに、プログラムの「型安全性」という点においてはどうでしょうか?
例えば代表的な AltJS の 1 つである TypeScript は静的型付け言語であり、非常に多機能な型システムを活用して型安全性の高いプログラムを書くことが可能です。
それに対して JS.tf には独自の型システムなども一切ありません。なんなら JavaScript の構文チェックすらもできないので、素の JavaScript 以上にガバガバで安全性は非常に低いと言っていいでしょう。
ここまで聞けば、もはや JS.tf を使わない理由は無いはずです。
まとめ
モダンとは?
Discussion