ほりひログ

所属組織の製品 (Azure とか) に関連する内容が多めだけど、個人の見解であって、所属組織を代表する公式情報ではないです。

Azure Functions/App Serviceでリモートビルドする時は、node_modulesを入れるな!

Azure Functions/App Service(以下、「Azure側」と書く。長いので。)へのデプロイで、デプロイパッケージのサイズを減らすために、リモートビルド時にnode_modulesをデプロイしない、というのは割とわかりやすい理由。
一方で、リモートビルド自体が失敗する、というパターンがあって、今回はこのパターンにハマった。

そもそもそうするものなんだろうから改めて書くほどのことではないけど、せっかくハマった原因を調べたので記録と戒めに。

ざっくりいうと

  • Azure側にアプリをデプロイする時にリモートビルドを使う*1と、デプロイ後にAzure上npm run buildなどが実行される。
  • この時、node_modulesも一緒にデプロイしていると、それらが悪さしてデプロイが失敗するケースがある。

というわけで、リモートビルドを使うなら、node_modulesは極力デプロイしちゃいけない(結論)。

リモートビルドは、プライベートなパッケージを使う時は.npmをいじる、などなど割と工夫が必要で、ビルド済みのパッケージをデプロイして Run from Packageした方がトラブルは少ない印象。

とはいっても、失敗する原因が気になったので、調べてみた*2

node_modules起因でリモートビルドが失敗するパターン

当然node_modulesの中にOS依存のバイナリとかを使っている時に、ローカル側とAzure側でOSを合わせないとうまく動かない。

けど、今回遭遇したのはそれとは別の話。

少なくとも

  1. Linux環境のローカル側でnode_modules/.bin/COMMANDを作っている。
  2. node_modules をデプロイパッケージに含めている。
  3. npm コマンドの postinstall / build / build:azure などで、1. で作った node_modules/.bin/配下のコマンドを実行している

の組み合わせでリモートビルドがコケる。

そしてこのパターンで出てくるエラーは大抵、

Error: Cannot find module '../lib/tsc.js'

みたいに、コマンド内から別ファイルへの参照に失敗している。

なぜデプロイが失敗するのか

Azure側でのリモートビルドでは、その初期段階でnpm installが改めて実行される。
けど、ここでインストールしようとする依存モジュールがデプロイパッケージ内のnode_modules配下にあると、そのモジュールのインストールをスキップして、デプロイパッケージに含まれているモジュール、つまりデプロイ前にローカル側でインストールしたモジュールを使おうとする。

この「デプロイ前にローカル側でインストールしたモジュール」が問題の原因。

npm がインストールするコマンド

「デプロイ前にローカル側でインストールしたモジュール」はどういうものなのか。

この node_modules/.binにインストールされるコマンドは、パッケージ側のpackage.jsonbinフィールドで宣言されているもの。
例えばtypescriptモジュールのtscコマンドの場合は、

のように宣言されている。
github.com

ここでnode_modules/.bin配下に作られるコマンドはOSごとに作られる内容が変わる。
Linux環境でbinフィールドを持つパッケージをインストールすると、binフィールドで指定されたファイル*3へのシンボリックリンク(以下、symlinkと書く)を作るらしい*4

実際にLinux環境でnpm install typescriptを実行してtscをインストールしてみると、
と、node_modules/.bin/tscnode_modules/typescript/bin/tscへのsymlinkになっていることがわかる。

リンク先の node_modules/typescript/bin/tscの中身は、

#!/usr/bin/env node
require('../lib/tsc.js')

と、単に別のjsファイルを読み込んでいる。

なので、関連ファイルの位置関係はこんな感じ。

node_modules
├── .bin
│    └── tsc             ---- (a)
└── typescript
        ├── bin
        │    └── tsc     ---- (b)
        └── lib
             └── tsc.js  ---- (c)

(a)がnpx tscで実行されるsymlinkファイルで、リンク先は(b)。
(b)はコマンドの実体で、相対パスで(c)を参照。 (c)はコマンド処理の本体。

途中、(b)から(c)へ相対パスでの参照があるけど、(a)が(b)のsymlinkであれば、(a)を実行しても(b)から(c)への参照は相対パスでも問題なし。
結果、npx tscnode_modules/.bin/tscなどで(a)を実行してもエラーは出ない。


ローカル環境ならね。

symlinkをデプロイするとどうなる?

じゃあこれらのnode_modules/.bin配下のsymlink達をデプロイパッケージに含めてAzure側にデプロイする*5とどうなるか。

デプロイされたsymlinkはこうなる*6

symlinkではなく、何らかのファイルそのものになっている。

じゃあその中身は、というと、

リンク先だったファイル(b)を丸々コピーしたものになっている。
うまいこと参照を解決するよう更新してるかといったら、そこまで優しくはなかった。

つまり、

  • Azure側にデプロイした時に、(b)へのsymlinkだった(a)は、(b)の単なるコピーに変わった
  • なので、(a)が直接(c)を参照する構成に変わった
  • でも(a)に書かれている(c)への相対パス ../lib/tsc.jsだと、(a)がある node_modules/.bin から(c)へ辿れない

結果、

Error: Cannot find module '../lib/tsc.js'

になった、と見て間違いなさそう。

デプロイ時にsymlinkがリンク先のコピーに置き換える処理は、VSCodeのAzure拡張の場合はここらへんでやってるっぽいので、正確には、デプロイパッケージを作った段階で、symlinkからコピーに置き換わっている。

github.com

(余談)Windows環境からデプロイ

ちなみにローカル側がWindows環境だと、もう少しややこしく、node_modulesを含めてデプロイしても、Azure側がWindowsでもLinuxでもリモートビルドが成功してしまうケースがある。

なぜか。
Windows環境でのnpm installは、symlinkを作らずに代わりに実行可能なスクリプトたちを作るから。

tsc を例にすると、Windows環境でnpm install typescriptを実行すると、

  1. PowerShell 用の tsc.ps1
  2. コマンドプロンプト用の tsc.cmd
    あと
  3. Cygwin/MinGW/MSYSなどのエミュレーター向けの tsc コマンド(ShellScript)

この3つがnode_modules/.binの中にできる。

node_modules
├── .bin
│    ├── tsc.ps1             ---- (1)
│    ├── tsc.cmd             ---- (2)
:    └── tsc                 ---- (3)

このうち(3)が割と柔軟で、tscのようにOSに依存しないファイルだけで構成されていれば、CygwinだけじゃなくWSLや通常のLinuxでも動いてしまう。

なのでAzure側のOSがWindowsだと(2)が、Linuxだと(3)が動き、結果どっちのOSを使っていようと tscが実行できてしまい、問題に気付きにくい。

ややこしい。

ということは、Windows環境での開発が最強なのでは?

とは当然ならなくて。

繰り返しだけど、OS依存のバイナリを使うパッケージがあると、ローカル側とAzure側でOSが違えば当然動かない。
なので、「node_moduleをデプロイするな」という結論は変わらない。

「どうしてもnode_modulesを含めたい!」

社内だけで公開しているパッケージなど、Azure側からインストールできないパッケージがある。
どうしてもデプロイパッケージにnode_modules入れないといけない。

そんな人は、リモートビルドは諦めよう。

「依存パッケージのインストール」->「ビルド済みパッケージ作成」->「Azureへのデプロイ」まで一気にやっちゃうCI環境を構築して、Azure側はRun from Packageで動かすのがいいと思う。

まとめ

結局のところ、以前からあるベストプラクティスに則る、が正解。


*1:アプリケーション設定で"SCM_DO_BUILD_DURING_DEPLOYMENT"を"true"とかにする

*2:あまり使い道のないは自覚している

*3:"tsc"の場合は、"./bin/tsc"

*4:https://docs.npmjs.com/cli/v10/configuring-npm/package-json#bin

*5:VSCode拡張機能やAzure Functions Core Toolsでのデプロイを想定

*6:postinstall で "ls -l node_modules/.bin/tsc" を実行して確認。

見られたくないテキストを隠すChrome Extensionを作ってみた(作ってる)

Chrome Extensionに興味が湧いたからやってみた。
まだ申請中なので本当に公開できるかわからんけど、いつの日か公開できると信じて告知エントリー。

何ができるのか。

見てもらった方が早い。

デモ動画

解説

Webページ自体はどこでもいい。今回はChrome ExtensionのAPI reference

  • [0:00-0:07] :
    ページ内の API を確認。
    見えている範囲で3か所ほど。
  • [0:07-0:14] :
    この拡張機能でキーワード API を設定。
    -> 最初に確認したページ内の API がボケる
  • [0:20-0:30] :
    拡張機能にキーワード extension を追加設定。
    -> API に加えて extension もボケる。
    • extensions は最後の s 以外はボケる。
    • Extension は処理対象外。先頭の E が違うから。

何がうれしいのか。

デモやスクリーンショットで、自分のPCを他の人と共有する機会があると、うっかり見られちゃいけない情報が映っちゃうことがある。
クラウドサービスのポータル画面にはアカウントや環境を示すIDが表示されるし、Google検索の結果にはおおよその居住地情報、Amazonのトップページには郵便番号が表示される。

どのページで何を表示しているかをあらかじめ網羅するのは割と大変。
なので、危ないキーワードはあらかじめ指定しておいて、指定したキーワードがデモや講義/セミナーなどで画面を見せる時にうっかり映りこんだ時に勝手にぼかしておくと嬉しいのでは*1

そうでもない???

導入方法

試してみたい人は、Chromeウェブストア、またはEdge アドオンから。




となるとカッコいいが、まだ審査中なので公開できていない。

なので、今のところソースコードから直接インストールするしかない。

GitHubから

ここで公開中。

github.com

  1. GitHubリポジトリからソースコードをとってくる。
    git clone でもzipファイルのダウンロードでも好きな方で*2
  2. Chrome/Edgeで chrome://extensions を開く。
  3. とってきたソースコードを読み込む。
    1. Chrome
      デベロッパー モード を有効にして パッケージ化されていない拡張機能を読み込む をクリック。
    2. Edge
      開発者モード を有効にして 展開して読み込み をクリック。

Chromeウェブストアから

拡張機能の性格上、処理を特定のページに限定してないせいので「審査に時間かかるよ」という警告(ってほどじゃないけど)メッセージにひるまず申請したが、無事却下。

却下理由は「使ってない権限付けないで」とのこと。

ハイ、消し忘れです。
キチンと見てるらしい。すばらしい。

ちょうど、申請した後に見つけたバグをつぶしたり内部構成を変えたりしてて、一度キャンセルして申請し直そうか迷ってたので、いいタイミングの却下だった。
更新したバージョンで再申請中。

ここで公開されるとChromeとEdge、両方に入れられる。

Edgeアドオンから

最初の審査の結果はまだ来てなかったけど、Chromeウェブストアでの再申請に併せてこちらも取り下げ&再申請中。
ちなみに、Chromeウェブストアと同じ内容(処理を特定のページに限定してないせい)で申請したけど、特に注意/警告の類いはない。

あと、ここで公開した拡張機能はEdgeにしか入れられない。

ご意見・ご要望

GitHubリポジトリissuesからお願いします。
日本語の方がありがたい。

*1:人に画面を見せる機会が少ない人には需要はなさそう

*2:git cloneだとリポジトリの更新に合わせるのが楽かもしれない。まだまだバグ退治中なので。

日本マイクロソフトを退職しました

手短に。

6/30付で日本マイクロソフト株式会社を退職した。
2018年、サポートエンジニアとして採用されてAzure PaaSのカスタマーサポート3年ちょっと、その後クラウドソリューションアーキテクトととして2年ちょっと、計5年半ほどのマイクロソフト歴だった。

Azure利用歴ゼロからスタートしたけど、クラウドサービスを支える仕組み・組織・人々まで知れて、中の人ならではな5年半だった。

次の職場でも当面Azure関連の仕事になるので*1、ここはAzureネタが続くんじゃないかな。

# ついでに日本語スタイルガイドの沿った文章の書き方もやめようと思う*2

取り急ぎご報告まで。


*1:次の職場のSNSポリシーがわからんので今のところ伏せとく。どうしても知りたい人はDM等で直接どうぞ。

*2:どうもこれは手が覚えてて、すぐには抜けなそうだが。

Azure Functions on Azure Container Apps


Kubernetes で Azure Functions が動くなら*1 Azure Container App でも動く?と思って勢いでやってみたら、割と簡単に動いた。

リソース構成

あまり普段意識しない子リソースがいくつかあって少しごちゃごちゃしてるけど、デプロイするリソース (「MCR」以外) はこんな感じ。

Container App は Azure Functions Host のコンテナ イメージ*2を動かす。
Storage Account は File ストレージで関数コードの保存先として、Container App から /home/site/wwwroot としてマウント*3

Application Insights は Azure Functions のテレメトリ/ログの送信先
実際は、近頃の Application Insights は Log Analytics でログを保存してるみたい。
なので Container Apps Environment 用の Log Analytics をそのまま流用。

Azure へデプロイ

試したい人は、こちらのカスタム デプロイのボタンからどうぞ。

Bicep はこちらで公開中。 github.com

他と比べてどうなのか

Azure Functions と比べて

イイところ。

Container App なので、組み込みの KEDA がスケーリングしてくれるので zero-scale もできる。 その上に VNET の中にデプロイできる。ELB / ILB 、どちらでも。

ちなみに Azure Functions の場合、zero-scale できる従量課金プランでは VNET 統合も Private Endpoint も使えない。
VNET が使える Elrastic Premium プランの場合は、zero-scale ができない。

おそらく一番の(そして唯一の?)アドバンテージかもしれない。

残念なところ。

タイマー トリガーの挙動が当初の期待と違った。
zero-scale すると当然ながらコンテナが無くなるので、スケジュールを管理できなくなりトリガーされない。
当たり前な動きだけど、タイマー トリガーの動作としては厳しい。
あとスケール アウトした時、全てのレプリカ上で関数が実行される。

一方で Azure Functions でホスティングしたタイマー トリガーは既定はシングルトン。
2 つ以上のインスタンスにスケール アウトしても関数は 1 つのインスタンス上でだけ実行される。
当然 zero-scale してもその直前にインスタンスが立ち上がり、スケジュール通りに実行する。

Azure Functions のスケール コントローラって、割と大変なことしているんじゃないだろうか。頭が下がる。

価格面は比較計算が難しい。
vCPU 1 コアあたりとかメモリ 1GB あたりの料金だけを (超局所的に) 比較すると、Elrastic Premium プランよりは安く見える。
が、これは使い方次第。

On Kubernetes と比べて

Kubernetesホスティングする場合、イベント ドリブンで Pod をスケーリングするに KEDA & Prometheus *4 が必要。
Azure Container Apps には元から入っているので、これらを入れる手間はなし。

Kubernetes も KEDA でスケーリングする以上、タイマー トリガー周りは違いはない。はず。と予想*5

VNET 内のリソースへのアクセスについては、AKSホスティングすればできるので、この辺りは特にアドバンテージなし。

価格は Pod のスケーリングよりもノード プール サイズの影響が大きいので、これまた比較が難しい。

比較まとめ

感想レベルだけども。

Azure Functions On Kubernetes On Azure Container App
Scaling
要 KEDA and Prometheus
Triggers
一部に限られる

(左に同じ)
VNET Injection
要 Elrastic Premium or ASP
Price
ユースケース次第なので比較困難

プラン & 使用量次第

ノード プール次第

vCPU/メモリ単価は安そう

VNET 内のリソースとの連携が必要であれば選択肢に入る、、、かも?


Azure Functions Node.js Framework v4 で作った関数を Azure 上にデプロイする

以前のエントリーで「新しいプログラミング モデル」と書いてたもの、どうやら 「Azure Functions Node.js Framework v4」というらしい。

github.com

長いので以下 V4 と書く。

Visual Studio Code の Azure Functions 拡張機能が、V4 用のテンプレートを作ってくれるようになったので試してみた。

プロジェクト作成手順

  1. まず Azure Functions 拡張機能Visual Studio Code に入れる。
    バージョンが1.10.0 以降になっているか確認すること。ちなみに 2023/2/9 現在、最新バージョンは 1.10.1。

  2. 次に設定 (Ctrl + ,) から Azure Functions: Show Node Programming Model を探してチェックを入れる。

  3. 設定が終わったら、これまで通り、空っぽのディレクトリで Azure Functions のプロジェクトを作成する。

  4. ウィザードの途中で「プログラミング モデルを選べ」と出てくるので Model V4 (Preview) を選択する*1

あとは関数も一緒に作るか、とか出てくるがそこは今までと同じなので省略。

すると、こういうファイル構成になる。
今回は TypeScript を選択し、HTTP トリガーの関数を一緒に作ってみた。

以前のエントリーで書いた通り、関数 httpTrigger1.ts があるディレクトリに function.json はない。
さらに、関数のディレクトリ自体も package.jsonmain の値で決まるので、どこにあってもいい。

TypeScript のテンプレートの場合、初期構成と tsconfig.json 内の設定やらが相まって、dist/src/functions 以下の JavaScript ファイルをインポートすることになっている。

関数コードの内容も、以前のモデルでは最低限関数を export するだけだったが、V4 では定義した関数とトリガー/バインディングをセットで app というオブジェクトに登録する処理が追加された。
V4 ではこの app への登録が重要で各関数を export しておく必要はない((けど、単体テストとかを考えると export しておいた方がよさそう))。

Azure Functions にデプロイしてみる

と、ローカルでのファイル構成の確認はここまでにして、さっそく Azure 上にデプロイしてみる。

まずは Function App リソースを作る。

ランタイムは当然 Node.js 、バージョンは 18 LTS。
OS もプランもどれでもいいはず。

出来上がったら Visual Studio Code からも確認。
作ったリソースを右クリックして、おもむろに Deploy to Function App... を実行。

npm install やら tsc といった前処理が動いて Azure 上へのデプロイも完了!

と思いきやログの最後が気になる。

No HTTP triggers found.

だって。 HTTP トリガー関数しか作ってないのにそれすらないとは。

Azure ポータルから見ても、確かにない。

V4 として認識されてない感じ。

冒頭のリンク先を見直してみると、Setup の最後にズバリ書いてあった。

なるほど、確かにローカルの local.settings.json (下図右) には AzureWebJobsFeatureFlags がある。
けど、デプロイしたての Function App (下図左) にはなかった。

なので、作ってみる。
Upload local settings...local.settings.json の内容をアップロードできるの便利。

できた。

関数も認識された。

テンプレ通りの関数なので動作結果は割愛。

聞いてみた

デプロイ先の Function App に AzureWebJobsFeatureFlags がなかったら何らかのメッセージが欲しいと思ったので、Azure Functions 拡張機能リポジトリに issue を立てたら、

そのフラグ、ホスト側の対応でもうすぐいらなくなるから。

とコメントがついて、わずか 1 時間で閉じられた。
自分が立てた issue で最速クローズかもしれない。

github.com

即レスもらえたのはいいことだ。
けど Teams のチャットかメールの方がよかったかもと思った。

(2/11 追記) Azure Functions Host の v4.15.2 で👆の「ホスト側の対応」が実施されたみたいなので、これが Azure 上でデプロイされれ AzureWebJobsFeatureFlags のくだりは不要になるはず。

*1:V4 なのは Model なのか Framework なのか。。。

日頃の行いが悪くて Azure Container Apps を VNET にデプロイできなくて反省した

2022 年の最後は反省 *1

事のいきさつ

これを試そうとした。

learn.microsoft.com

Azure Container Apps を VNET にデプロイするやーつ。
ごくごく普通にドキュメント通りにやればいいだけ。
Azure ポータルからも Azure CLI からもできる、と書いてある。

これを読んで、普段いろんなリソースをつないで VPN 接続もできる VNET に Azure Container Apps のデプロイしてみた。

 ︙
 ︙

ダメ。

タイムアウトするという謎(当時は)の事象。

The resource provision operation did not complete within the allowed timeout period.

曰く、リソースのデプロイが所定の時間内に終わらなかった、と。


詳細もよくわからない。

「たまたまプラットフォーム側に負荷が高いだけ?」と思って何度かトライしても結果は一緒。

Azure CLI からやってみる。

 ︙
 ︙

やっぱりダメ。

もう少しヒントがでた。

ErrorCode: ManagedEnvironmentConnectionBlocked, Message: Fail to create managed environment because outbound connectivity was blocked, refer to https://go.microsoft.com/fwlink/?linkid=2198255 for more detail.

何やら、Outbound 接続に問題がある様子。

でもこれ以上はわからない。

テナントを疑ってみる

デプロイ先の VNET が、普段検証作業をしている割と厳しめなテナント*2にあるので、別のテナントで試してみる。

 ︙
 ︙

一発で成功。

ん?元のテナントは何かの制限をされてる?
でも社内の人は同じテナントを使っているはずなのに、こんなトラブル聞いたことない。

VNET を疑ってみる

デプロイ失敗した VNET と同じテナントに新しい VNET を作って、新 VNET の方にデプロイしてみる。

 ︙
 ︙

こちらも成功。
同じテナントなのに。
てことは、デプロイしようとしている VNET リソース固有の問題か。

問題解決は突然に

完全に VNET リソースが怪しいので、各サブネットにつけられてた NSG を片っ端から外したり、全ブレード*3をくまなく調査。

ふと初心に戻り [概要] ブレードを開いた時に目が行った。

そう、この VNET リソース、DNS サーバーを独自に設定している。
なんでかっていうと、 手元の PC でこの VNET 内にある Private Endpoint の動作確認等のために VPN でつなぐことがあるため。

同じ VNET 内に Azure Container Instances で DNS フォワーダーを立てて、その IP アドレスを VNET の DNS サーバーとしていた。
この DNS フォワーダーは普段シャットダウンしてて、PC から VPN する時だけ起こしている。

DNS フォワーダーの話はこちら。
uncaughtexception.hatenablog.com

そして今は Container App をデプロイしたかっただけで、VPN 接続はしてないので、当然 DNS フォワーダーは起こしていない。

あー、、、完全にこれだなー。。。

DNS フォワーダーを起こして再デプロイ。

 ︙
 ︙

一発 OK。

、、、 ですよね。

反省

自分がやった設定くらいは覚えておけ!

唯一得られた知見は、Container Apps Environment を VNET にデプロイする時、VNET の DNS 設定に従って何らか Outbound 通信が発生している、ということ。
まぁ、普通は DNS サーバー設定をいじってても名前解決できる状態にしとくだろうから、デプロイできるんだろうけど。

以上。

よいお年を。

*1:多分、こんな状況になるのは自分だけだと思うのでただの与太話エントリー

*2:この会社管理は、Azure Policy か何か(詳しくは知らない)で NSG を自動でつける

*3:リソースを開いたときに右側に縦に並んでいる項目。人によっては「タブ」とも言ってるが一応「ブレード」が正式名称、のはず

Azure Functions で Top-Level await は使えるのか? New Programming model 編

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.jsonmain フィールドに書いたファイルがエントリー ポイントになるので、このファイルが import 文 or import 関数で呼ばれる条件がわかればいい。

特に仕様書的なのはないので、ソースコードを探索。

ユーザーがデプロイした関数コードを読む個所は /src/loadScriptFile.ts の 8 行目あたりから。

14 行目で eval('import(fileUrl.href)') というのが見えるので import 関数を使って Dynamic Import している。 このブロックに入るには 10 行目で 関数 isESModuletrue を返せばいい。

じゃあ、その関数が true を返す条件はなんなのか、関数の中身を見てみる。

同じファイルの 24 行目から始まる関数 isESModuletrue を返す条件は以下の 2 つ。

  1. 指定されたファイルの拡張子が .mjs
  2. package.jsontype フィールドの値が module
    ただし、ファイルの拡張子が .cjs 以外*3

このうち 1. の拡張子 .mjs については、現状のプログラミング モデルでもできた方法。
2. については、プログラミング モデルの刷新で package.json を扱うことになったのでできるようになったこと。
npm で公開されているような通常の Node.js のモジュールと同じような扱いができるようになった。

TypeScript は?

TypeScript で実装した場合は、TypeScript コンパイラ tsc によって、デプロイ パッケージの時点で JavaScript のコードになっている。

現状のモデルでは package.jsontype フィールドが使えないので、拡張子 .mjs 一択。
それには、.mts という拡張子でコードを書くか*4.ts でコードを書いて .js として出力されたファイルを .mjs に変換する処理を自前で書く必要があった。

一方で新しいモデルでは package.json が使えるようになったので、.ts で書いて .js で出力されたとしても👆の 2. で対応可能。

ただしどちらを使っても tscコンパイル結果を CommonJS ではなく ESModule として出力する必要がある。
なので、tsconfig.jsonESModule として出力する設定をする。ここも前回と変わらない。

試した感じ、今なら以下の moduletargetmoduleResolution の書き方でいけそう。

{
  "compilerOptions": {
    "module": "ES2022",
    "target": "ES2017",
    "moduleResolution": "node",
    :
}

サンプル

流石に焼き増しが過ぎるエントリーので、一応サンプルも。

github.com

おなじみの 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:このやり方、今回初めて知った。前回もこれでいけたのかも。