Microsoft Azure Tech Advent Calendar 2022 の 3 つ目?の 23 日目。
はじめに
Azure Functions の Node.js ランタイムで新しいプログラミング モデル*1の開発が進んでいるのは以前のエントリーの通り。
uncaughtexception.hatenablog.com
Azure ではまだ使えない。リリースが待ち遠しい。
そのエントリーの中でコソッと書いたこれ。
その後ちゃんと検証せずに放置していたので改めて見てみた。
いきなり結論
使える。
# 既視感。
まぁ、モデルを変えたタイミングでトレンドからあえて外れる、ってことはないだろうから、これは予想通り。
ちゃんと解説
# 中身よりもサンプルを試したい人はコチラ。
前回も書いた通り、Top-level await は ES modules の仕様*2。
なので、関数コードが ES modules として扱われる必要がある。
新しいプログラミング モデルでは、ユーザーがデプロイした関数コードのパッケージに含まれる package.json
の main
フィールドに書いたファイルがエントリー ポイントになるので、このファイルが import
文 or import
関数で呼ばれる条件がわかればいい。
特に仕様書的なのはないので、ソースコードを探索。
ユーザーがデプロイした関数コードを読む個所は /src/loadScriptFile.ts の 8 行目あたりから。
14 行目で eval('import(fileUrl.href)')
というのが見えるので import
関数を使って Dynamic Import している。
このブロックに入るには 10 行目で 関数 isESModule
が true
を返せばいい。
じゃあ、その関数が true
を返す条件はなんなのか、関数の中身を見てみる。
同じファイルの 24 行目から始まる関数 isESModule
が true
を返す条件は以下の 2 つ。
- 指定されたファイルの拡張子が
.mjs
package.json
のtype
フィールドの値がmodule
ただし、ファイルの拡張子が.cjs
以外*3
このうち 1. の拡張子 .mjs
については、現状のプログラミング モデルでもできた方法。
2. については、プログラミング モデルの刷新で package.json
を扱うことになったのでできるようになったこと。
npm で公開されているような通常の Node.js のモジュールと同じような扱いができるようになった。
TypeScript は?
TypeScript で実装した場合は、TypeScript コンパイラ tsc
によって、デプロイ パッケージの時点で JavaScript のコードになっている。
現状のモデルでは package.json
の type
フィールドが使えないので、拡張子 .mjs
一択。
それには、.mts
という拡張子でコードを書くか*4、.ts
でコードを書いて .js
として出力されたファイルを .mjs
に変換する処理を自前で書く必要があった。
一方で新しいモデルでは package.json
が使えるようになったので、.ts
で書いて .js
で出力されたとしても👆の 2. で対応可能。
ただしどちらを使っても tsc
のコンパイル結果を CommonJS
ではなく ESModule
として出力する必要がある。
なので、tsconfig.json
で ESModule
として出力する設定をする。ここも前回と変わらない。
試した感じ、今なら以下の module
、target
、moduleResolution
の書き方でいけそう。
{ "compilerOptions": { "module": "ES2022", "target": "ES2017", "moduleResolution": "node", : }
サンプル
流石に焼き増しが過ぎるエントリーので、一応サンプルも。
おなじみの Puppeteer で指定のページを PDF として取れる API を新しいモデルで実装(サンプルとしてわかりやすいので)。
.devcontainer も載せたので、Dev Container か GitHub Codespaces で試せるはず。
せっかくなので今回から使える package.json
に "type": "module"
を書く方法で ES module として認識させている。
https://github.com/horihiro/20221222-func-nodejs-prototype-toplevelawait/blob/main/package.json#L5
Top-level await はここ。
https://github.com/horihiro/20221222-func-nodejs-prototype-toplevelawait/blob/main/src/index.ts#L4
このコードの場合、Chromium を起動する launch
メソッドに必要な await を関数の外で使えるので、アプリ起動のタイミングで Chromium を一度立ち上げておける。
関数呼び出し時は、起動時に立ち上げたインスタンスを使いまわせる。
比較として、現状のモデル & Top-level await 無しのサンプルはコチラ。
あまり大したことないように見えるけど、アプリの中で 1 回だけ実行したいDB 接続の初期化処理などは、最近 async で定義されているものが多い。 それを関数の外で使いたいのは割とあるシーンじゃないだろうか。
でも結局
まだ Azure 上では使えない。
Python の新しいモデルは Public Preview なので、Node.js もはよ。
*1:コードネーム的なのはないんだろか
*2:https://github.com/tc39/proposal-top-level-await
*3:.js を想定していると思うが、ここまで他の拡張子のファイルが到達しないかは未確認
*4:このやり方、今回初めて知った。前回もこれでいけたのかも。