Python
JavaScript
Node.js
Express.js
Scratch

Node.js + Express で HTTPサーバを作りコンパクトな実行形式にする(Scratch 2 拡張を例に)

Node.js 版 Scratch 2 HTTP拡張ヘルパーを作ろう

Node.js + express で作成したものを、zeit/pkg を使って各OSの実行形式ファイルにするテストも行います。
ブラウザ(Chromium)は含める必要がないため、Electron より軽量(小さいファイルサイズ)になるはずです。

https://github.com/memakura/scratch2-ex-nodejs

Scratch 2 とHTTP拡張ヘルパー

HTTPサーバ (ヘルパーアプリ) を用意すると、ブロックプログラミングの Scratch 2(オフライン版)に独自機能(拡張ブロック)を加えることができます。
こちらの記事ではPython + aiohttpでヘルパーを作成しましたが、これを Node.js + Expressで作成してみます。さらに、それを Node.js がインストールされていない環境で使用するために、 Windows の実行形式にしてみます(Mac や Linux も出来ますが実行できるかは手元に環境がないため未テスト)。

ここで作成するヘルパーは簡易HTTPサーバ

Scratch 2 (offline版) の拡張ブロックには、Scratch側からコマンドを送る「コマンドブロック」(四角い形のブロック)と、Scratch側で値を受け取る「レポーターブロック」(角の丸い形のブロック)など、いくつかの種類があります。いずれも Scratch側から GET を送っており、値を受け取るときもポーリング方式をとっています。したがって、Scratch からの GET を待ち受けるような簡易ローカルHTTPサーバを作成してそこで何か処理を行うようにすれば、新たな機能を拡張ブロックとして追加できます。

command_and_reporter.png

こちらの記事のように、Python版では aiohttp を使って以下のようなコードにしました。

testhelper.py
from aiohttp import web

volume = 0

async def handle_poll(request):
    text = "volume " + str(volume) + "\n"
    return web.Response(text=text)

async def handle_beep(request):
    print("play beep!")
    print("\007")
    return web.Response(text="OK")

async def handle_setvolume(request):
    global volume  # 忘れずに
    tmp_volume = int(request.match_info['vol']) # いったん別の変数へ
    if tmp_volume >= 0 and tmp_volume <= 10:
        volume = tmp_volume
        print("set volume= " + str(volume))
    else:
        print("out of range: " + str(tmp_volume))
    return web.Response(text="OK")

app = web.Application()
app.router.add_get('/poll', handle_poll)
app.router.add_get('/playBeep', handle_beep)
app.router.add_get('/setVolume/{vol}', handle_setvolume)

web.run_app(app, host='127.0.0.1', port=12345)

なお volume という変数名ではありますが、この実装では音量が変わるわけではないです・・(単に値のやりとりのテストです。)

Express + Node.js 版

上記のコードを Express + Node.js で作ると以下のようになります。

scratch-ex.js
"use strict";

var express = require('express');
var app = express();

var server = app.listen(12345, function(){
    console.log("Server start... listening port " + server.address().port);
});

var volume = 0;

app.get('/poll', (req, res) => {
    res.send("volume " + volume);
});

app.get('/playBeep', (req, res) => {
    console.log("play beep!");
    process.stderr.write("\x07"); // to prevent new line (instead of console.log)
    res.send("OK");
});

app.get('/setVolume/:volume', (req, res) => {
    var tmp_volume = req.params.volume;
    if(tmp_volume >= 0 && tmp_volume <= 10){
        volume = tmp_volume;
        console.log("set volume= " + volume);
    }else{
        console.log("out of range: " + tmp_volume);
    }
    res.send("OK");
});

実行するにはまず Node.js および Express をインストールしておきますが、たとえばこの記事が参考になります。

> mkdir new_project
> cd new_project
> npm init

順に答えていき、"main" はファイル名(上の例ではscratch2-ex.js)にします。また、このディレクトリ内にファイル scratch2-ex.js を置いておきます。

> npm install express --save
> node scratch2-ex.js

これで Express サーバが実行されます。

zeit/pkg で実行形式にする

実行形式にするには Electron を使うこともできますが、インストール後のサイズがかなり大きくなります(Hello world でも 100MB を越える)。Electrino など、Electron を軽量化するプロジェクトもあるようですが、今回はコンソールアプリでブラウザ機能は不要なので、(EncloneJSの後継の)zeit による pkg というツールを使ってみます。他にも node-packer (nodec) というのもありましたが、pkg の方がすんなり動いたので今回はこちらにします。

インストールは非常に簡単です。

> npm install -g pkg

使い方も簡単で、以下のコマンドで Windows (x64) の実行形式にできます。

> pkg -t win-x64 scratch2-ex.js

これで scratch2-ex.exe ができるので、実行して試してみます。

Scratch 2 オフライン版を立ち上げ、シフトを押しながら「ファイル」メニューをクリックし、「実験的なHTTP拡張を読み込み」からこのような感じのJSON形式のファイルを読み込んで試してみます。詳しくは以前の記事を参照してください。
Scratch 2 からのポーリングに反応していれば、赤丸が緑色になります。

-t はターゲット指定で、nodeのバージョン、OS (freebsd, linux, macos, win)、アーキテクチャ (x64, x86, armv6, armv7) などを指定できます。

> pkg scratch2-ex.js

のように省略すると、linux, macos, win がデフォルト値で一度に作成されます。詳しくは https://github.com/zeit/pkg を確認してみてください。Windows 以外のプラットフォームについては機会を見てテストしてみることにします。