2021-07-30 ,

EmacsのIndiumでJavaScript開発を試す

Emacs用のJavaScript開発環境であるIndiumを試してみました。

NicolasPetton/Indium: A JavaScript development environment for Emacs

一口に開発環境と言っても何が出来るのかよく分からないと思いますが、

  • Emacs側で入力したJavaScriptを実際の実行環境上で実行(評価)して結果を出力する
    • 入力毎に逐次実行するいわゆるREPL
    • 自由に書いて自由に実行できるscratchバッファ
    • JavaScriptソースコード内のコードを実行
  • デバッガー(ステップ実行や値の調査など)

といったことが出来るようです。

実行環境はChromeとNode.jsに対応していて、どちら用のJavaScriptでも実行したりデバッグしたり出来ます。Chromeで言えばデベロッパーツールとしてコンソールやデバッガーがありますが、あれがEmacsから操作できるといえば分かりやすいでしょうか。

私が試してみたきっかけはorg-modeのJavaScriptコードブロック(ob-js.el)がこのIndiumと連携可能だと書いてあったからなのですが、それはまた後ほど。

indiumでデバッグしているところ
図1: indiumでデバッグしているところ

手元の環境

  • Windows 10
  • Emacs 27.2
  • Google Chrome 92
  • Node.js 12

あたりが既に入っています。

インストール

  1. npm install -g indium

    Node.jsでIndiumサーバをインストールします。

  2. M-x package-install indium

    Emacsにindiumパッケージ(Indiumクライアント)をインストールします。

  3. indium-chrome.el の indium-chrome-data-dir部分を変更します。(2021-07-29時点)

    (defvar indium-chrome-data-dir
      (make-directory indium-chrome--default-data-dir t)
      "Chrome profile directory used by Indium.")
    

    ↓へ変更。

    (defvar indium-chrome-data-dir
      (progn
        (make-directory indium-chrome--default-data-dir t)
        indium-chrome--default-data-dir)
      "Chrome profile directory used by Indium.")
    

    必要に応じて.elcを削除したりコンパイルし直したりしてください。既にロードしてしまっている場合は変数の値を再設定してください。

    これをやらないとChromeがエラーを出します。

    Chromeの起動時に出たエラー。nilディレクトリ
    図2: Chromeの起動時に出たエラー。nilディレクトリ

    IndiumはデフォルトだとChromeを独立したプロファイルで起動します。そのプロファイルを置くディレクトリをmake-directoryで作るのは良いのですがmake-directoryの戻り値であるnilを変数に入れてしまっています。

プロジェクト設定を行う

  1. プロジェクトディレクトリを作る

    まずは何をするにもプロジェクト設定ファイルが必要です。その設定ファイルが置いてある場所を基準に色々な動作が決められているので、まずはそれを置くプロジェクトディレクトリを作りましょう。

  2. プロジェクト設定ファイルを作る

    プロジェクトディレクトリの下に .indium.json というファイルを作ります。

    内容は次の通り。

    {
        "configurations": [
            {
                "name": "Local Host 8080 Page",
                "type": "chrome",
                "url": "http://localhost:8080/index.html"
            }
        ]
    }
    

    nameは適当な名前を付けてください。

    typeは実行環境の種類です。chromeかnodeが指定出来るみたいです。今回はchrome。

    urlはtypeにchromeを指定した場合のオプションで、Chromeからどのページを開くか(デバッグ対象にするURL)を指定します。Chromeから開けるページならどこでもOKです。普通は自分の開発中のページを指定することになりますが、他人様のページを指定することも出来ますし(変なデータを送りつけるのは止めましょう)、ローカルファイル(file:///)を指定することも出来ます。

    例えば次のようにするとGitHubのIndiumのページがデバッグ対象になります。

    {
        "configurations": [
            {
                "name": "Indium Page",
                "type": "chrome",
                "url": "https://github.com/NicolasPetton/Indium"
            }
        ]
    }
    

    また次のようにするとローカルにあるファイルがデバッグ対象になります。

    {
        "configurations": [
            {
                "name": "My Local Directory",
                "type": "chrome",
                "url": "file:///home/hoge/indium-tutorial/index.html"
            }
        ]
    }
    

Windowsでローカルファイルを指定するときの注意点

Windowsでurlにローカルファイル(file:/// プロトコル)を指定した場合、デバッグ(ブレークポイントの設定)が正しく行えない問題に遭遇しました。

調べてみたところ、例えば file:///C:/home/hoge のようなURLが /C:/home/hoge のように頭にスラッシュが残されたパスに変換されてしまう箇所があるようです。例えばindiumサーバのソースコード server/helpers/workspace.js の resolveUrl 関数に fileプロトコルの時の処理があるのですが、ここで /C:/C:/ に変換していないためにWindowsではおかしなパスになってしまいます。他にも問題があるようで、そこを直しただけでは解決しませんでした(逆変換とかがどこかにある?)。その上のconvertRemoteRootを見るとremoteRootオプションで指定した文字列を空文字列で置き換えています。この動作を利用すれば問題を回避することが可能です。

回避方法ですが、remoteRootオプションにプロジェクトディレクトリへのフルパスを頭にスラッシュを付けて指定します。

{
    "configurations": [
        {
            "name": "My Local Directory(Windows)",
            "type": "chrome",
            "remoteRoot": "/C:/home/hoge/indium-tutorial/",
            "url": "file:///C:/home/hoge/indium-tutorial/index.html"
        }
    ]
}

remoteRootオプションがあると、その文字列を手元のプロジェクトディレクトリに置き換えてくれます。これでスラッシュから始まる不正なパスを正しいパスへ置き換えることが出来ます。

httpサーバの起動

urlに localhost:8080 などと指定した場合は自分で別途Webサーバを起動する必要があります。お手軽に起動できるサーバが色々あるみたいなので好きな物を使ってください。

Emacsで完結するならsimple-httpdがお手軽です。 M-x httpd-serve-directory だけで完了です。

色々遊ぶ

  1. まずプロジェクトディレクトリ下にindex.htmlを作ります。

    <!DOCTYPE html>
    <html>
      <head>
        <title>Test</title>
      </head>
      <body>
        <p id="hello">hello, world</p>
      </body>
    </html>
    
  2. プロジェクトディレクトリ下で M-x indium-launch を実行します。

    するとデバッグ用オプションの付いたChromeが起動してurlオプションで指定したページが開きます。(indium-chrome-data-dir変数の場所にChromeプロファイルが出来てその上で実行されるはず)

    Emacs側では *JS REPL* という名前のバッファが開きます(以下REPL)。

  3. REPLに document.getElementById("hello").innerText; (単に hello.innerText でも良い)と入れると "hello, world" が返ってきます。
  4. REPLに document.body.insertAdjacentHTML("beforeend", "Konnichiwa!"); と入れるとブラウザに Konnichiwa! と表示されます。
  5. プロジェクトディレクトリの下にexample.jsを作ります。

    var cv = document.createElement("canvas");
    cv.width = 640;
    cv.height = 480;
    var ctx = cv.getContext("2d");
    function draw(color){
        ctx.fillStyle = color || "blue";
        ctx.fillRect(0, 0, 640, 480);
        document.body.appendChild(cv);
    }
    draw();
    
  6. REPLに次のように入れるとexample.jsがロードされて画面に青い矩形が出ます。

    var script = document.createElement("script");
    script.src = "example.js";
    document.body.appendChild(script);
    
  7. REPLに次のように入れると赤い線が出ます。

    ctx.lineWidth = 2;
    ctx.strokeStyle = "red";
    ctx.moveTo(0, 0);
    ctx.lineTo(640, 480);
    ctx.stroke();
    
  8. Emacsでexample.jsを開いてdraw()関数内の一行目にブレークポイントを置きます。 M-x indium-add-breakpoint か、または M-x indium-interaction-mode の後に C-c b b でブレークポイントを置けます。
  9. REPLに draw("red"); を入れるとブレークポイントの位置にカーソルが飛びます。
  10. l を押すとローカル変数一覧が出ます。colorが"red"になっていることが分かります。 SPC でステップ実行したり c で実行を再開したりできます。
  11. M-x indium-quit で接続を切ります。専用プロファイルで起動しているChromeも閉じましょう。

Node.jsでも遊ぶ

Node.js用のJavaScriptをデバッグすることも出来ます。

新しくプロジェクトディレクトリを作り、設定ファイルを作り、適当なJavaScriptファイルを作ります。

まずは設定ファイル(.indium.json)を作ります。

{
    "configurations": [
        {
            "name": "Example",
            "type": "node",
            "program": "node",
            "args": "./example.js",
            "inspect-brk": true,
            "remoteRoot": "/c:/home/hoge/indium-node-example/"
        }
    ]
}

typeはnodeとします。

argsに実行するJavaScriptファイルを指定します。

inspect-brkがtrueだと最初でブレークします。

Windowsでは残念ながらNode.jsでも /c:/ で始まるパスの問題でうまくデバッグが出来ませんでした(M-x indium-list-script-sources で確認できます)。それを回避するためにremoteRootを指定しています。

適当なJavaScriptファイル(example.js)を作ります。

function concat(a, b){
    return a + b;
}
const hw = concat("hello, ", "world");
console.log(hw);

あとはこのディレクトリで M-x indium-launch すれば最初の所(上の例では const hw のところ)に飛びます。ステップインしてconcat関数の中に入り、 *JS REPL* バッファで a とか b とか打ってみてください。変数の値が確認できます。

org-modeのコードブロック(babel)から使う?

Org-babel-jsによればorg-modeのJavaScriptコードブロック(ob-js.el)はIndiumに対応しているようなこと書かれています。ヘッダー引数に :session "*JS REPL*" を指定するのだとか。しかし(プロジェクトファイルを作った上で)実際にやってみると org-babel-execute:js: Symbol’s function definition is void: indium-run-node というエラーが出ます。Indiumには現在 indium-run-node という関数は無いのでどうやら現在は動かなくなってしまっているようです。

IndiumにもIssueが上がっています。

Question: org-mode ob-js.el sessions · Issue #234 · NicolasPetton/Indium

org-modeから使うなら自分で改造する必要があると思います。

Pingback / Trackback