木曜日午後〜日曜日午前中の4日間,Vim コミュニティつながりの知人と旅館に泊まり込んでもくもく作業する合宿的な旅行に行ってきました.
当日の様子や旅館の便利情報についてはすでにブログ記事にまとめられているのでそちらを読んでいただいて,この記事内では僕がやった作業内容を備忘録的に書いておきます.
今回やったこと
- neovim-component と NyaoVim の Polymer v2 対応
- clever-f.vim のテストを themis.vim に移行
- clever-f.vim のテストカバレッジを covimerage を使って取る
- Vim プラグインの試作(まだ途中)
neovim-component と NyaoVim の Polymer v2 対応
今回の一番でかい成果はウェブアプリフレームワーク Polymer v1 を使って書いていたアプリを v2 にアップデートしたことでした.
NyaoVim という Neovim のフロントエンドなデスクトップアプリを Polymer と Electron を使ってつくっています.Neovim フロントエンドというのは,裏でヘッドレスな Neovim を起動して標準入出力経由の RPC で描画情報やユーザのキー入力などをやりとりすることで Neovim の表面部分を実装したものです.NyaoVim では Electron をフレームワークとして使うことで Neovim エディタのスクリーン部分を WebComponents のカスタム要素として描画することで,元来 Vim にはなかった,ユーザが自由に UI を拡張できる仕組みをウェブの技術を用いて提供します.
Web Components と Electron でつくる Neovim フロントエンドの未来 github.com
このアプリは Polymer という WebComponents をベースにしデータバインディングを乗っけた薄いコンポーネントライブラリを使って実装しています.ユーザは設定を <template>
要素と JavaScript を使って HTML ファイル(nyaovimrc.html
)で書きます.<template>
を使うことで自由にカスタム要素で作られた UI プラグインを配置でき,挙動を Polymer のデータバインディングを使ってカスタム要素の属性を使ってカスタマイズできます.NyaoVim を作り始めた当初は Polymer v1.3 ぐらいで,それ以降も v1 を使い続けていましたが,さすがにそろそろ v2 に移行すべきだと思って半年ぐらい経ってしまったので良い加減やることにしました.
Polymer v1 は WebComponents v0 時代につくられたということもあり,Polymer v2 では WebComponents v1 の仕様への乗り換えが行われました.それもあってか,カスタム要素定義の仕方やライフサイクルの扱いが大きく変わっています(見た感じデータバインディングについては大きくは変更なし). 公式にある Polymer 2.0 アップグレードガイド を読みつつ下記の手順で進めました.
- レガシースタイルのまま v1 から v2 に乗り換える
- ES2015
class
を使った新しいスタイルに乗り換える - 壊れた部分やテストを直す
- 関連 UI プラグインを Polymer v2 向けに修正する
最初の時点でまずは依存する Polymer のバージョンを v2 に上げます.Polymer v2 は移行のために v1 のレガシーなスタイルもある程度対応しているので,まずはバージョンだけ上げてアプリを起動してみてうまく動いていないことを確認し,修正していきます.
Polymer v1 までは外からもカスタム要素の中身が見えるのがデフォルトになっていたため,カスタム要素を構成する要素を取るのに雑に document.querySelector
を使ってしまっていたのですが,v2 からは完全に見えなくなる(document.querySelector
で要素を取ろうとしても null
になる)ので,Polymer の API (this.$
など)を使って要素を取ってくるように正しました.
また attached
コールバックの発火タイミングが変わっていて DOM の要素のサイズが取れなかった(NyaoVim では Neovim 起動時にエディタのサイズ(行数と桁数)を指定するためにフォントサイズと要素サイズから計算する必要がある)ので,Polymer.RenderStatus.beforeNextRender
を使って,要素の first paint(次の request animation frame 発火直後)まで処理を遅延するようにしました.
Polymer({ is: 'x-foo', // ... attached: function() { // <x-foo> が DOM に挿入された直後に実行される Polymer.RenderStatus.beforeNextRender(this, function() { // <x-foo> がページ内に描画された直後に実行される }); } });
次に Polymer v2 からは独自のファクトリ関数ではなく WebComponents v1 のカスタム要素のように ES2015 class
を使って要素を定義することができるので,それを使ってレガシースタイルの要素を class
による実装に移行します.
class XFoo extends Polymer.Element { static get is() { return 'x-foo'; } static get properties() { return {...}; } } customElements.define(XFoo.is, XFoo);
Polymer v2 は標準の API を直接使えるところはなるべく使うという方針なので,カスタム要素の定義は標準の customElements.define
を使います.また,v1 のレガシースタイルで用意されていた attached
, connected
や disconnected
といったライフサイクル系のコールバックは標準の attachedCallback
や connectedCallback
,disconnectedCallbback
などのカスタム要素標準のコールバックを使って書き換えていきます.また,ユーザがデータバインディング経由でカスタム要素の属性の値を変更した場合は同様に標準の attributeChangedCallback
を使って知ることができます.NyaoVim は TypeScript で書いていて,DefinitelyTyped の型定義は v1 のもので古かったのですが, v2 向けの型定義を書いている人を発見したので拝借して使わせてもらいました.
最後に,これらの変更によって壊れたテストや NyaoVim の UI を拡張するためのプラグイン(markdown プレビューやツールチップなど)の修正を行いました.E2E テストでカスタム要素の中を直接覗いていたのが v2 で不可視になったことによりテストできなくなったため,一番外側のカスタム要素に生やしてあったプロパティを経由するようにしたりなどの変更をしました.
まだできていないこと:
- React の DOM を ShadowRoot を貼り付けたカスタム要素下の子要素にマウントすると React コンポーネント
<div onClick={...}\>
などのコールバックがなぜか発火しない - Web Component の中身が見えなくなったので,E2E テストでコンポーネントの中身をテストできなくなってしまった.web-component-tester あたりを使えば良さそう
UIプラグインの中に React で動的な処理を書いたアプリをカスタム要素で wrap したものがあったのですが,onClick
が発火しなくなってしまい使えなくなってしまいました.誰か何か知っていたら教えてほしい…
clever-f.vim のテストを themis.vim に移行しテストカバレッジを取れるようにした
clever-f.vim は Vim の f
系マッピングを拡張するプラグインで,使い続けているプラグインの1つです.
最近 covimerage という Vim の profilng 機能による実行ログを利用して Vim script のカバレッジを取るツールが(一部で)話題になっていて,@haya14busaさんのおかげで,どうやら結構簡単に導入できそうだということで導入してみました.
clever-f.vim には50弱のテストケースがあって,今までは vim-vspec を使ってテストを書いていたのですが,どうやら vim-vspec はテスト前 (bootstrap.vim)に特定の処理を挟むことができないらしいので,themis.vim を使って書き直しました.:profile
でプロファイルを取る処理を入れる必要があります.
themis.vim は :Describe
や :It
を使った spec スタイルをサポートしているのでほぼ問題なく全てのテストを移行できました.
次に Travis CI を使って Linux と macOS で covimerage をインストールし,.themisrc
で :profile
を使ってテスト中の profile を取り,covimerage を使ってカバレッジレポートを生成して codecov にアップロードする処理を入れます.
Dashboard ⋅ rhysd/clever-f.vim
このように Vim プラグインのテストカバレッジを取ることができました.今後はテストできない箇所のテストをちまちま追加していきたいと思っています.
1点うまくいかなかったのは,clever-f.vim の migemo 対応をする部分のコード がなぜか「UTF-8 でデコードするには不正なシーケンスが含まれている」というエラーで covimerage ではうまくパースできず外さざるをえなかった点です.
もくもく合宿の所感
タスクを詰め込みすぎない(どうせ全部できない)
3泊4日ということもあり,タスクを詰め込みがちですが,詰め込んでもどうせできないのでその日に必ずこれをやるというのを設定しておくのが良さそうでした.もくもく会は独りで集中して作業するのに比べると作業速度は落ちるので,あまり期待しすぎないのが大事な気がしました. 自分のリポジトリに来た issue 対応を合間にやったり,とあるアプリに初めてメールで脆弱性レポートが送られてきたり,他のメンバーの Splatoon2 プレイを見ていたのもあって,メインの作業は思ったほど進みませんでした.
ただその分,他のメンバーが踏んだ興味深いバグを一緒に追ってみたり,手元で温めているアイデアを他メンバーと議論・相談したり,分からないところのヘルプを頼んだりできて有意義だったと思います.(i.e. autoload
内のスクリプトは :source
や runtime
で読んではいけないことを知った)
2泊3日がベストっぽい
実は今年の4月頃にも一度同じところに1泊2日で行ったのですが,1泊2日は短すぎたので今回は3泊4日に挑戦という流れでした.3泊4日はしっかり時間が取れて1泊2日より良かったですが,若干だれてしまう感じだったので2泊3日が自分の中ではベストかなぁと思いました.
尊師スタイル
今回は HHKB を持っていったので,内蔵キーボードを Karabiner で無効にして尊師スタイルしてみたところ,(MacBook Pro 2016 late の残念なキーボードに比べて)はるかに打ちやすくて最高でした.これからもくもく会はこのスタイルで行きます.しかしセパレート型の持ち運び向けキーボードがベストっぽい…