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のコードブロックから使えるのではないかと期待したからなのですが、結局どちらも使えなかったのにはガッカリしました。