Author Archives: misohena

2021-08-01 ,

EmacsのSkewerでライブWeb開発を試す

先日IndiumというJavaScriptの開発環境を試してみましたが、今回はそれと似た用途に使えるSkewerというものを試してみました。

skeeto/skewer-mode: Live web development in Emacs

SkewerはEmacsでライブWeb開発をするためのツールだそうです。

Emacs側で入力したJavaScriptをブラウザ側で実行する仕組みがコアになっています。Indiumと同じようにREPLやソースファイル内のJavaScriptをブラウザ側で実行できます。

また、その仕組みを応用してHTMLやCSSの現在編集している部分をブラウザに反映させる機能もあります(skewer-html-mode, skewer-css-mode)。

変更とリロードを繰り返すのでは無く、編集した部分だけを逐次ブラウザに反映させていきながら開発するスタイルということでライブ開発と呼んでいるのでしょう。

インストール方法

M-x package-install skewer-mode

simple-httpd や js2-mode も一緒に入ります(入っていなければ)。

JavaScriptの実行(ソースコード上とREPLバッファ)を試す

skewerサーバの起動

M-x run-skewer

→ ブラウザで 127.0.0.1:8080/skewer/demo が開きます。

(動作の詳細: M-x run-skewer を実行するとEmacsで実装されたhttpサーバが起動しローカルホスト(127.0.0.1:8080)の /skewer というURLでEmacs側と通信するスクリプトが取得できるようになります。 /skewer/demo ページはそのスクリプトを読み込んでいます。読み込まれたスクリプトは /skewer/get/skewer/post 等のURLにアクセスしてEmacs側と通信し、Emacs側からの要請に応えてブラウザ側で動作します)

(M-x list-skewer-clients を実行するとクライアントが一つ接続されていることが確認できます)

REPLを試す

  1. M-x skewer-repl

    *skewer-repl* というバッファが開きます。

  2. REPLに alert("hello"); と入れる

    → ブラウザ側でアラートダイアログが出ます。

  3. REPLに document.documentElement.innerHTML と入れる

    → バッファに "<head>\n <title>Skewer</title>\n <script src=\"/skewer\"></script>\n </head>\n <body>\n \n\n</body>" と出ます。(使っているブラウザの拡張機能によってはもっと複雑なコードが出る場合もあります。Evernote Webクリッパー!)

ソースファイルの実行を試す(skewer-mode)

  1. 適当な場所にexample.jsというソースファイルを作る(私は普段からjs2-modeを使っています)

    var cv;
    
    if(!cv){
        cv = document.createElement("canvas");
        cv.width = 640;
        cv.height = 480;
        document.body.appendChild(cv);
    }
    
    var ctx = cv.getContext("2d");
    ctx.clearRect(0, 0, 640, 480);
    ctx.fillStyle = "#28f";
    ctx.fillRect(0, 0, 640, 480);
    
    ctx.fillStyle = "#4c2";
    ctx.fillRect(0, 360, 640, 120);
    
  2. Emacsでソースファイルを開いて M-x skewer-mode
  3. ソースファイル内で C-c C-k (skewer-load-buffer) を実行する

    → ブラウザに絵が表示されます。

  4. ソースコード内の色(#xxxの部分)や座標などをいじって何度も C-c C-k

    → 都度変更が反映されます。

  5. C-c C-z

    → REPLバッファが出ます。

  6. REPLに document.body.innerHTML と入力する

    → うわぁ、見るんじゃなかった。どうやってロードしているのかが分かります。

HTMLの更新を試す

  1. M-x httpd-serve-directory で好きなディレクトリを指定

    → 127.0.0.1:8080/ でアクセスできるディレクトリ(ルート)がそのディレクトリになります。

  2. そのディレクトリの下にindex.htmlを作る

    <!DOCTYPE html>
    <html>
      <head>
        <title>Skewer Example</title>
        <script src="/skewer"></script><!-- これが重要 -->
      </head>
      <body>
        <p id="hello">hello, world</p>
      </body>
    </html>
    
  3. ブラウザで 127.0.0.1:8080/index.html を開く
  4. Emacsでindex.htmlを開き M-x skewer-html-mode
  5. worldをworlddddに変更して その場所で C-M-x (skewer-html-eval-tag)

    → ブラウザに反映されます。

    C-M-xではカーソル(ポイント)を置いている要素のみ更新されます。ただしbodyは更新出来ません。

    色々試してみると分かると思いますが、要素の順番を入れ替えたりすると正しく更新されなかったりします。

他にもskewer-css-modeを使えばcssファイルの更新も行えるようです。

任意のページをskewerと接続する

上の例はhtmlにscriptタグを埋め込まなければならないのがちょっとイケてませんよね。

実は必ずしもscriptタグを埋め込まなければならないわけではありません。要は http://127.0.0.1:8080/skewer からJavaScriptを読みさえすれば良いのです。

例えばブラウザのアドレスバーから javascript: プロトコルでスクリプトを読み込むという方法があります。次の文字列をアドレスバーに打ち込むとその時開いているページにskewerのスクリプトが読み込まれEmacsと繋がります。

javascript:(function(){var d=document;var s=d.createElement('script');s.src='http://localhost:8080/skewer';d.body.appendChild(s);})()

このURLをいわゆるブックマークレットにすれば簡単に好きなページとEmacsを接続できます。

他にも開発者用のコンソールからこのコードを実行する方法もあるでしょう。

Emacsパッケージに入っているskewer.jsを別サーバへコピーしてそこから読み込むのはNGです。あくまで M-x run-skewer で起動したEmacs上のhttpdサーバから読み込む必要があります。

この方法で任意のページ(ローカルファイルを含む)でREPLする手順は次のようになります。

  1. M-x run-skewer
  2. 好きなページをブラウザで開く
  3. 次のコードをそのページで実行する(ブックマークレットにしておくとクリック一つで実行できます)

    (function(){var d=document;var s=d.createElement('script');s.src='http://localhost:8080/skewer';d.body.appendChild(s);})()
    
  4. M-x skewer-repl

    *skewer-repl* というバッファが開きます。

  5. REPLに document.body.insertAdjacentHTML("afterbegin", "<p>hello!</p>"); などと打ち込む

    → ページの先頭にhello!と表示されます。

手打ちでhtmlやcssを編集しているときに思いついたら接続して更新を確認しながら作業するといったことが出来そうですね。

終了方法

ブラウザでskewerと繋がっているページを全て閉じます。閉じると M-x list-skewer-clients (gで更新)に出てくるクライアントが消えます。

M-x httpd-stop でhttpサーバが止まります。

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

Org-babel-jsによればorg-modeのJavaScriptコードブロック(ob-js.el)はSkewerに対応しているようなこと書かれています。ヘッダー引数に :session "*skewer-repl*" を指定するのだとか。しかし実際にやってみると executing Js code block... というメッセージが出たまま待てど暮らせど反応が返ってきません。ob-js.elを見てみると org-babel-js-initiate-session でreplバッファが無いか無効ならskewerを起動しているのですが、新しく作成したREPLバッファではなくその無い(nil)か無効かのバッファを返しています。そこを直してみても今度はコードをブラウザに送り出した後反応が返ってきません。

Issueに上がっていた方法を試してみましたがこちらもサーバ側でエラーが出て正しく動作しませんでした。

Modification: make org babel js blocks use skewer when it is connected · Issue #65 · skeeto/skewer-mode

解決するにはもう少し詳しく調査する必要がありそうです。

SkewerとIndiumの比較

SkewerもIndiumもEmacs側のJavaScriptをブラウザで実行できるという点は同じです。

単にREPLがしたいだけならSkewerの方が少しだけ簡単でしょうか。Indiumは必ず設定ファイルを作らなければなりませんしデバッグ機能を有効にしたChromeを起動しなければならないので。

依存する外部ツールが少ないのもSkewerの良いところです。Node.jsは不要ですしブラウザもChromeに限定されません。

幅広いブラウザで使用できるのもSkewerの良いところです。IndiumはChromeのリモートでバッグ機能が必要ですが、Skewerはページに専用のスクリプトを注入することで実現しています。

HTMLやCSSを視野に入れている点もSkewerの良いところです。IndiumはあくまでJavaScriptの開発環境です。ただ、SkewerのHTMLやCSSの部分更新は(原理上)正しく機能しない場合も多々あります。

Skewerには本格的なデバッグ機能はありません。ブレークポイントを置いたりステップ実行したいならIndiumを使う必要があります。

SkewerはNode.js用のJavaScriptには対応していません。JavaScript開発というよりはあくまでWeb開発のためのツールです。

Skewerは侵入的であるのに対してIndiumは非侵入的です。Skewerはページにスクリプトを注入しなければなりません。それがページの動作に干渉する可能性はゼロではないでしょう。また、スクリプトを注入する方法を色々考えなければなりません。EmacsからブラウザへJavaScriptを読み込むときもbodyにscript要素を追加することで実現しています。気がつくとbodyがscriptだらけになっていることがありました。

全体的な完成度はChromeのデバッグ機能を使うという筋の良さも手伝いIndiumの方が高い気がしますが、一方で致命的なバグやドキュメントの不親切さも目立ち今回試すにあたってIndiumはかなりハマりました。

今回SkewerやIndiumを試したのはorg-modeのコードブロックから使えるのではないかと期待したからなのですが、結局どちらも使えなかったのにはガッカリしました。

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から使うなら自分で改造する必要があると思います。

2021-07-29

新しい寝タブ用タブレットスタンドを購入

以前寝タブ用にタブレットスタンドを購入したのですが最近はほとんど使っておらず布団の脇に放置されていました。というのも

  1. アームが短く頭のすぐ近くに置かなければならないこと
  2. 頭の近くにあると使わないときに邪魔なので遠ざけたいがアームが曲げづらく土台ごと遠ざけなければならないこと
  3. 土台ごと遠ざけると次に使うのが億劫になること(土台は布団の下に挟み込まれているので簡単には動かない)

あたりが原因だと思います。他にもアームが曲げづらいので使っている間の調整が難しいこともあると思います。

使わないアームが布団の脇にずっと放置されているのも何だか不愉快になってきたので、代わりのものを探してみたところ良さそうな商品が見つかりました。

タブレットスタンド スマホ ホルダー 360度回転可能 38cm-140cm高さ調節可能 主体調節でき 折り畳み式 フレキシブルアーム 寝ながら 根元強化 下垂防止 3.5~10.6インチ 便利スタンド

これこれ、こういう昔ながらの電器スタンドのアームみたいなタイプが欲しかったんです。以前探したときにはなかなか見つからなかったのですが最近はタブレットスタンドのバリエーションもかなり増えてきたのかもしれませんね。

届いたので早速組み立てて使ってみました。

一番下の直線部分は2本のパイプをパイプに切られたネジで連結する仕組みになっています。1本だけ使って低くすることも可能。それを土台のネジに繋げます。

直線パイプの上、一番最初の可動部分はアームパーツを下のパイプ上部の穴に差し込むだけになっています。なのでこの部分が垂直軸まわりに360度自由に動きます。邪魔なときにアームをよけておくのに使えます。

アームはかなり広範囲に動きます。パイプ2本だと高いかなと思ったのですが、アームによってかなり低い位置まで下ろせます。もちろん高い位置にもできるので目からの距離を使用中に柔軟に調整できます。

唯一の欠点はタブレットを挟む部分がタイトなことでしょうか。バネによってタブレットを挟んで固定するのですが、10インチタブレットだとかなりギリギリです。挟む力がかなり強いので挟みっぱなしだとそのうち本体が変形しないか心配になります。まぁ、多分大丈夫だとは思いますが……。

というわけでかなり満足度の高いタブレットスタンドでした。