Azure FunctionsでNode.jsのバージョンを変更する

Azure Functions v2 runtimeではNode.jsを任意バージョンに切り替えられるようになった。前から試そうと思ってたのと、いつまでたってもドキュメントに反映されないのと両方あるので、とりあえず手順を書いておく。

やること自体は、ここに書いてある通り。 github.com

Function App の設定 を開いて、ランタイムバージョンを v2 に切り替える。(結構時間がかかる) f:id:terurou:20171223195704p:plain

次に アプリケーション設定 を開いて、 WEBSITE_DEFAULT_NODE_VERSION に使いたいNode.jsのバージョンを指定して、保存する。f:id:terurou:20171223195912p:plain

これだけで完了。念のため プラットフォーム機能 から コンソール を開いて、正しく設定されているかを確認。

> node -v

f:id:terurou:20171223200033p:plain

Haxe/JSでMainなし(EntryPointなし)のプロジェクトを作る

2回ぐらいやり方を忘れてググるみたいな感じになっているので、いい加減メモっておく。

code.haxe.org

要は、通常のHaxeプロジェクトでは、.hxml を

-cp src
-main Main
-js bin/main.js

みたいに設定するところを、

-cp src
Main
-js bin/main.js

のように、-main を削った書き方にすればよい。

dynabook V82/Dを買って、Windows 10 1709をクリーンインストール

dynabook V82/Dを買いました。

元々はLet'snote RZ-5を使っていたんですが、10インチサイズの筐体が仇になり、キーボードが小さくてキー入力ミス率が上がっていたり、長時間キー打つのは疲れたり、打ち合わせ相手にディスプレイを見せるときに小さすぎて見えづらいと言われたり、割と散々な感じだったので、乗り換え先を探していて、候補に残った感じです。

  • いちおう開発もできるスペック
  • クラムシェル型
    • タイプカバーというかキックスタンドなタイプだと、新幹線で使いづらい
  • タブレットモードになる
    • 打ち合わせ相手に見せるときに便利
  • ペン入力ができる、しかもワコム
    • デザイナーと打ち合わせする際に、PDFに書き込みとかを割とやる
    • ペンは標準付属、某Surfaceみたいな別売りとは違う
    • 実際に書いてみたけど、確かにこだわっているだけあって、書きやすい
  • 本体1100g + ACアダプター260g + USB-Cアダプター100gぐらいで、ぎりぎり持ち運びができる許容範囲内
    • 買ってから気が付いたけど、筐体にHDMI/VGA出力がなくて、本体充電と共用のUSB-C/Thunderboltポート1つしか付いてない(MacBookと同じ)ので、プロジェクターに接続するような場合は、USB-C アダプターが必要
    • 専用のUSB-Cアダプターは標準で付属してたのと、実測で98gだったので、まぁ許容範囲内…
    • ACアダプタは260gぐらいあるけど、コンセントとつなぐ側のメガネケーブルが50g強あったので、L字プラグとかに変えると軽くなる
      • ACアダプタはUSB-C PD 45Wなので、純正ではない軽いやつに変更もできるはず
    • バッテリー公称17時間あるので、ケースが合えば本体のみの持ち運びも十分考えられる
  • 割と安い
    • キャンペーン中だったっぽいけど、税込17万円強
    • 同じぐらいのスペックで某Surfaceとかと比べると…

富士通のLIFEBOOK WU2/B1 or WU2/B3ともかなり迷ったんですが、軽さよりもペン入力を取ってdynabook V82/Dにしました。

で、この辺りはマイナスポイントですが、特に国内メーカーPCだと余計なソフトウェアが色々入っているので、買って早々にWindowsクリーンインストールしました。東芝PCのクセを把握せずに作業していたので、1回インストールに失敗して、リカバリーしたりしています…。

  1. TOSHIBA Recovery Media CreatorでUSBリカバリーメディアを作成する
  2. オリジナルドライバー、ソフトウェアをバックアップする
    • C:\TOSAPINS フォルダ全て
    • C:\Program Files\TOSHIBA\TOSAPINS フォルダ全て
  3. Windowsクリーンインストール
  4. バックアップしておいたオリジナルドライバー群を、元と同じパスに配置
  5. C:\TOSAPINS\HTML\SETUP\index_top.html を起動して、必要最低限のドライバーおよびソフトのみをインストール
    • 「ドライバ」タブに表示されているドライバー類全て
    • 「ユーティリティ」タブの以下
      • Intel Chipset SW Installation Utility(チップセットドライバー)
      • TOSHIBA System Settings
      • TOSHIBA eco Utility(いわゆるいたわり充電の設定に必須)
      • 任意:DTS Studio Sound(サウンド設定)
      • 任意:TOSHIBA Service Station(アップデート通知、ハードウェアチェック)
    • その他は必要ならインストール
  6. 4.で展開したオリジナルドライバー群のファイルを削除(当然残しておいても構わない)

これで必要最低限の環境になったはずですが、しばらく様子見を見します。

Haxeのマクロで外部プロセスに処理を移譲する場合の知見

HaxeでVue.jsの開発をするために、.vueファイル(Single File Component)コンパイラをNode.jsで実装して、マクロから呼び出してコード生成するということをやっていた。このエントリーを書いている時点では、コンパイラ自体はまだ「とりあえずコードが生成できている」というレベルなのだが、マクロから外部プロセスを起動する部分については知見が溜まってきたのでメモを残す。

なお、この記事で使用しているHaxeはv3.4.3である。Windows 10でしか動作確認していないが、おそらく他のOSでも挙動は同じになると思われる。

プロセスの起動方法

次のコードが基本形となる。

gist.github.com

ポイントとしては、

  • sys.io.Processを使う。
  • プロセスの起動確認にstdoutを読み込んでいる。これはstdoutが読めるまでブロッキングされる。
  • 外部プロセス側で「起動が完了したことが分かる何らかの値」をstdoutに出力しなければならない。
  • プロセスの起動が失敗するなどの要因で、プロセス側からstdoutがクローズされ、終端を読み込むとEofがthrowされてくる。

この方法だと、外部プロセス側で処理が完了したら、successfulでもなんでもいいので、なんらかの起動完了を表す文字列をstdoutに出力しなければならない。これがないと、readLine()で処理がブロッキングされてしまう。

上記以外に起動確認をする方法としては、リターンコードの確認(process.exitCode())をする方法もある。これもリターンコードが返されるまでブロッキングされる。ただ、後述する外部プロセスを常駐化することを考えると、この方法は利用できない。

また、補足として、process.stdoutの読み込みも当然ながらブロッキングされるため、こちらを監視して正常起動を確認するという方式はとることができない。

その他注意点

  • プロセス起動は比較的オーバーヘッドが大きいので、マクロ内で何度もプロセス起動をすべきではない。
    • 厳密な計測ではないが、手元の環境(Xeon E3-1241 v3)で試した際は、プロセス起動では300msec強かかるのに対して、localhostに対してのHTTPであれば20msec未満でリクエスト処理が完了する。
  • stdoutに改行なしで大きなデータ出力される場合、応答がなくなる?
    • 最初は都度プロセスを起動して、処理結果をstdoutに出力してデータのやり取りをする素朴な実装をしていたのだが、process.stdout.readAll().toString() という実装ではマクロの応答がなくなってしまう問題が発生した。readLine()の場合はデータサイズが大きくても問題は発生していない。
    • 原因未調査だが、プロセスが停止されている(=stdoutが閉じられている)状況でもデータのサイズが大きいと応答がなくなってしまうため、Haxeの標準ライブラリ側に問題がある可能性もあり。
  • stdoutでテキストデータのやり取りをする場合、Windows環境では文字エンコーディングの問題が生じる。

上記のような問題に遭遇し、色々試したのだが、マクロ起動時にコンパイラをサービスプロセスとして起動して、HTTP RPCで都度の処理を移譲する返す形に落ち着いた。

マクロ起動時に処理を実行するには、Haxeコンパイラパラメータで--macro https://haxe.org/manual/macro-initialization.html を指定する。また、Haxelib化している場合は、extraParams https://haxe.org/manual/haxelib-extraParams.html と併用すれば、ライブラリを参照するだけで自動で --macro が付与されるようにできる。

また、sys.io.Processは明示的にclose()もしくはkill()しなくても、マクロ終了時にプロセスを停止してくれるのだが、Haxeコンパイラの入力補完機能を利用している場合は事情が異なってくる。

この場合は、Haxeコンパイラプロセスが常駐状態になるので、入力補完が走るたびにプロセス起動処理が呼び出され、明示的に外部プロセスをクローズしない限りは起動しっぱなしになる。そのため、マクロ側かプロセス側のどちらかで(できれば両側で)多重起動を防止するコードが必要となる。マクロ側で対応する場合は、単純にsingletonとして処理すれば十分である。

追記:Haxeコンパイラの入力補完サービスの場合、Haxeコンパイラプロセスが停止しても起動した外部プロセスは停止されないようだ。これは明示的にハンドリングする方法がないようなので、ある程度お行儀をよくするには、外部プロセス側で一定期間利用されていなければ自動終了させるような仕組みを入れるしかないと思われる。

思い付きでHaxe/JavaScript extern用のabstract型を作ったら幸福度が上がった

HaxeJavaScript用のexternを書く際、EtherType(Union Typeを表現する型)とRest(可変長引数を表現する型)が存在する。

ただ、「EtherTypeは型を2つしか指定できないからなー」という感じの思考から、思い付きでちょっとabstract型を定義してみたら思いのほかexternを書くのが楽になるものができた。見たらわかるレベルのものなので、解説省略。

Kotlin/JavaScriptを試してみた

Kotolinとは

ググって

KotlinでJavaScriptにトランスコンパイル

スタンドアロンコンパイラを使う方法とGradle等のJVMビルドツールを使う方法がある。実際に開発すると仮定すると、インクリメンタルビルドが可能なGradleを使うことになると思われる。

手順としては、

  1. JREをインストール(JVMAndroid向けの開発も行う場合はJDKが必要だが、JavaScript開発の場合はJREで良い)
  2. Gradleをインストール(当然、Gradle Wrapperを使ってもよい)
  3. プロジェクトのスケルトンを作る(build.gradle)

という感じになる。

Hello World

ブラウザでHello Worldが動くところまでを試してみた。IntelliJ IDEA等のIDEを使えばプロジェクトテンプレートがあるかもしれないが、IDEを使わずにテキストエディタのみでやってみた。

ここに記載する手順は、次の環境で確認した。

  • Windows 10 Creators Update
  • JDK 8
  • Kotlin 1.1.4
  • Gradle 4.1

まずビルドファイルを作る。任意の空ディレクトリ内で次のように build.gradle ファイルを作成する。 ext.kotlin_version の部分は、使いたいKotlinのバージョンを指定する。

group 'org.example'
version '1.0-SNAPSHOT'

buildscript {
    ext.kotlin_version = '1.1.4'
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    }
}

apply plugin: 'kotlin2js'

repositories {
    mavenCentral()
}

dependencies {
    compile "org.jetbrains.kotlin:kotlin-stdlib-js:$kotlin_version"
}

task assembleWeb(type: Sync) {
    configurations.compile.each { File file ->
        from(zipTree(file.absolutePath), {
            includeEmptyDirs = false
            include { fileTreeElement ->
                def path = fileTreeElement.path
                path.endsWith(".js") && (path.startsWith("META-INF/resources/") || 
                    !path.startsWith("META-INF/"))
            }
        })
    }
    from compileKotlin2Js.destinationDir
    into "${projectDir}/web"

    dependsOn classes
}

assemble.dependsOn assembleWeb

次にMainとなるコードを記述する。

fun main(args: Array<String>) {
    println("Hello World")
}

ビルドは gradle build コマンドを実行する。ビルドに成功すると、 web ディレクトリ以下にJavaScriptが生成される。

ブラウザで実行するために、HTMLファイルを作成する。ここでは作業ディレクトリ直下にHTMLファイルを作成する(webディレクトリ配下にHTMLファイルを配置してしまうと、ビルド時に消えてしまう)。

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
</head>
<body>
<script type="text/javascript" src="web/kotlin.js"></script>
<script type="text/javascript" src="web/kotlin_main.js"></script>
</body>
</html>

作成したHTMLファイルをブラウザで読み込み、開発者ツールのコンソールを確認すると、 Hello World と表示されているはずである。

試してみた感想

実用レベルだが、自分で積極的に使うことはないかなぁというのが正直な感想。

実用となる根拠としては、

  • DOM操作が可能
  • js()関数で生JavaScriptコードの埋め込みが可能
  • Dynamic型でJavaScript側のオブジェクトが操作可能
  • CommonJS, AMD形式のモジュールとしてビルド可能
  • SourceMapが出力可能
  • dead code elimination (DCE) が使える

というあたり。

一方で積極的に使うことはないなぁという根拠としては、

  • 実行時にKotlin Runtime (kotlin.js) に依存しなければならず、非圧縮状態で1.3MB程度ある

というところが個人的に最大のネックになる。kotlin.jsはminifyすれば数百KB程度になると思われるが、どうせRuntimeに依存しないといけないのであれば、私ならScala.jsを選択する。

Scala.jsの場合、

というメリットがあるため、Kotlinの現状では、チームメンバーがKotlinに精通しまくっているということでもない限りは選ぶことはない。

(蛇足として、そもそもHaxeおじさんなのでScala.jsも積極的には選択しないのだけど、自社の人間はScala.js経験値を積み上げているという…