Azure FunctionsでNode.jsのバージョンを変更する
Azure Functions v2 runtimeではNode.jsを任意バージョンに切り替えられるようになった。前から試そうと思ってたのと、いつまでたってもドキュメントに反映されないのと両方あるので、とりあえず手順を書いておく。
やること自体は、ここに書いてある通り。 github.com
Function App の設定
を開いて、ランタイムバージョンを v2
に切り替える。(結構時間がかかる)
次に アプリケーション設定
を開いて、 WEBSITE_DEFAULT_NODE_VERSION
に使いたいNode.jsのバージョンを指定して、保存する。
これだけで完了。念のため プラットフォーム機能
から コンソール
を開いて、正しく設定されているかを確認。
> node -v
Haxe/JSでMainなし(EntryPointなし)のプロジェクトを作る
Firefox 57以降で、ブックマークを新しいタブで開く方法
about:config
で browser.tabs.loadBookmarksInTabs
を true
に。
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回インストールに失敗して、リカバリーしたりしています…。
- TOSHIBA Recovery Media CreatorでUSBリカバリーメディアを作成する
- 32GBのUSBメモリーが要求された…
- オリジナルドライバー、ソフトウェアをバックアップする
- C:\TOSAPINS フォルダ全て
- C:\Program Files\TOSHIBA\TOSAPINS フォルダ全て
- Windowsをクリーンインストール
- バックアップしておいたオリジナルドライバー群を、元と同じパスに配置
- C:\TOSAPINS\HTML\SETUP\index_top.html を起動して、必要最低限のドライバーおよびソフトのみをインストール
- 4.で展開したオリジナルドライバー群のファイルを削除(当然残しておいても構わない)
これで必要最低限の環境になったはずですが、しばらく様子見を見します。
Haxeのマクロで外部プロセスに処理を移譲する場合の知見
HaxeでVue.jsの開発をするために、.vueファイル(Single File Component)コンパイラをNode.jsで実装して、マクロから呼び出してコード生成するということをやっていた。このエントリーを書いている時点では、コンパイラ自体はまだ「とりあえずコードが生成できている」というレベルなのだが、マクロから外部プロセスを起動する部分については知見が溜まってきたのでメモを残す。
なお、この記事で使用しているHaxeはv3.4.3である。Windows 10でしか動作確認していないが、おそらく他のOSでも挙動は同じになると思われる。
プロセスの起動方法
次のコードが基本形となる。
ポイントとしては、
sys.io.Process
を使う。- プロセスの起動確認にstdoutを読み込んでいる。これはstdoutが読めるまでブロッキングされる。
- 外部プロセス側で「起動が完了したことが分かる何らかの値」をstdoutに出力しなければならない。
- プロセスの起動が失敗するなどの要因で、プロセス側からstdoutがクローズされ、終端を読み込むとEofがthrowされてくる。
この方法だと、外部プロセス側で処理が完了したら、successfulでもなんでもいいので、なんらかの起動完了を表す文字列をstdoutに出力しなければならない。これがないと、readLine()
で処理がブロッキングされてしまう。
上記以外に起動確認をする方法としては、リターンコードの確認(process.exitCode()
)をする方法もある。これもリターンコードが返されるまでブロッキングされる。ただ、後述する外部プロセスを常駐化することを考えると、この方法は利用できない。
また、補足として、process.stdout
の読み込みも当然ながらブロッキングされるため、こちらを監視して正常起動を確認するという方式はとることができない。
その他注意点
- プロセス起動は比較的オーバーヘッドが大きいので、マクロ内で何度もプロセス起動をすべきではない。
- stdoutに改行なしで大きなデータ出力される場合、応答がなくなる?
- 最初は都度プロセスを起動して、処理結果をstdoutに出力してデータのやり取りをする素朴な実装をしていたのだが、
process.stdout.readAll().toString()
という実装ではマクロの応答がなくなってしまう問題が発生した。readLine()
の場合はデータサイズが大きくても問題は発生していない。 - 原因未調査だが、プロセスが停止されている(=stdoutが閉じられている)状況でもデータのサイズが大きいと応答がなくなってしまうため、Haxeの標準ライブラリ側に問題がある可能性もあり。
- 最初は都度プロセスを起動して、処理結果をstdoutに出力してデータのやり取りをする素朴な実装をしていたのだが、
- 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型を作ったら幸福度が上がった
HaxeでJavaScript用のexternを書く際、EtherType(Union Typeを表現する型)とRest(可変長引数を表現する型)が存在する。
ただ、「EtherTypeは型を2つしか指定できないからなー」という感じの思考から、思い付きでちょっとabstract型を定義してみたら思いのほかexternを書くのが楽になるものができた。見たらわかるレベルのものなので、解説省略。
Kotlin/JavaScriptを試してみた
Kotolinとは
ググって
KotlinでJavaScriptにトランスコンパイル
スタンドアロンコンパイラを使う方法とGradle等のJVMビルドツールを使う方法がある。実際に開発すると仮定すると、インクリメンタルビルドが可能なGradleを使うことになると思われる。
手順としては、
- JREをインストール(JVMやAndroid向けの開発も行う場合はJDKが必要だが、JavaScript開発の場合はJREで良い)
- Gradleをインストール(当然、Gradle Wrapperを使ってもよい)
- プロジェクトのスケルトンを作る(build.gradle)
という感じになる。
Hello World
ブラウザでHello Worldが動くところまでを試してみた。IntelliJ IDEA等のIDEを使えばプロジェクトテンプレートがあるかもしれないが、IDEを使わずにテキストエディタのみでやってみた。
ここに記載する手順は、次の環境で確認した。
まずビルドファイルを作る。任意の空ディレクトリ内で次のように 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の場合、
- 最初からビルドツールチェーンでminifyが考慮されている(fullOptJS指定時、Google Closure Compilerが使われる)
- Scala/JDKのクラスライブラリが一部JavaScriptコードに変換される https://www.scala-js.org/libraries/libs.html
- 単純にScala.jsの方がユーザーが多い(と思われる)
というメリットがあるため、Kotlinの現状では、チームメンバーがKotlinに精通しまくっているということでもない限りは選ぶことはない。
(蛇足として、そもそもHaxeおじさんなのでScala.jsも積極的には選択しないのだけど、自社の人間はScala.js経験値を積み上げているという…