🎠

vim沼: JavaScriptでElectronとReact Nativeアプリを効率的に開発する設定

2020/10/21に公開

先に英語で書いてから日本語訳しています。

こんにちは、個人アプリ作家のTakuyaです。

僕はInkdropというMarkdownノートアプリを独りで開発しています。
これはmacOSやWindows、Linux、iOSからAndroidまでスムーズに動作します。
なぜならデスクトップ版はElectron、モバイル版はReact Nativeで組まれているからです。
つまりアプリは基本的にJavaScriptで書かれています。

Inkdrop

本稿では、vimにて効率的にJavaScriptをコーディングするためのワークフローについてシェアします。
僕はVSCodeのようなIDEを使わず、主にターミナル上で作業しています。
使っているツールはtmuxとNeovimです。
この構成での基本的なワークフローについてはこちらに書きました
ここでは、更にvimの設定について掘り下げてご説明します。
僕のdotfilesはGitHub上で公開していますので、ご自由にコピペしてください。

Plugins

使っているvimのプラグインは以下の通りです:

dein.nvim - プラグイン管理

プラグインのインストールやアップデートの管理にはdein.nvimを使っています。
使いたいプラグインはdein.tomlファイルに、以下のような感じで記述します:

[[plugins]]
repo = 'Shougo/dein.vim'

[[plugins]]
repo = 'Shougo/defx.nvim'
depends = ['defx-git', 'defx-icons']
hook_add = """
source ~/.config/nvim/plugins/defx.rc.vim
"""

[[plugins]]
repo = 'Shougo/denite.nvim'
hook_add = """
source ~/.config/nvim/plugins/denite.rc.vim
"""

[[plugins]]
repo = 'jiangmiao/auto-pairs'

[[plugins]]
repo = "neoclide/coc.nvim"
merge = 0
rev = "release"
hook_add = """
source ~/.config/nvim/plugins/coc.rc.vim
"""

もう一つファイルがあります。dein_lazy.tomlというファイルです:

[[plugins]]
repo = 'elzr/vim-json'
on_ft = ['json']

[[plugins]]
repo = 'yuezk/vim-js'
on_ft = ['javascript', 'javascript.jsx']

[[plugins]]
repo = 'maxmellon/vim-jsx-pretty'
on_ft = ['javascript', 'javascript.jsx']

このファイルは開いているファイルタイプに応じて都度読み込まれるプラグインを記述します。
例えばvim-jsonというプラグインはjsonファイルを開いた時にのみ読み込まれるようにしています。
そうすることで、その時必要ないプラグインは極力読み込まず、vimの起動や動作を常に軽快に保つことが出来ます。
ここでは僕が普段よく使うファイル形式に関するプラグインを書いています。

coc.nvim - インテリセンス

coc.nvimConquer of Completionの略です。かっこいいですね。
これはあなたのvim環境にてインテリセンスを実現するためのプラグインです。
例えば、オートコンプリーション、オートインポート、タイプ定義といった、IDEがよく備えている補助系機能を提供します。素敵。

例えば以下のようなTypeScriptがあったとします:

type Note = {
  _id: string,
  body: string,
  title: string,
  createdAt: number,
  updatedAt: number,
  tags: [string]
}

const note: Note = {
  _id: 'hige',
  body: '# hello',
  title: 'example note',
  createdAt: 'moji'
}
console.log('note:', note)

createdAtはnumber型であるべきです。
でももし間違えて文字列を入れてしまった時、以下のようにその間違いを教えてくれます:

TypeScript error

number型であるべきですよ、文字列じゃないですよ、と。
coc.nvimはこういった事をしてくれます(厳密にはLanguage Serverです)。

オートコンプリーションも以下のようにやってくれます:

auto completion

ご覧のように、ツールチップでタイプの定義を表示してくれます。

もちろん関数の定義表示もできます。
hoge.ts というファイルがあって、そこにBookクラスやgetThingsDone関数が定義してあったとします。
すると、getThinなどと打つだけで以下のように補完してくれます。

type definition

定義の説明もこのように表示してくれます。
そこで「おっしゃ挿入しよう」とエンターを押します。
するとimportステートメントも自動で挿入してくれます。

import {getThingsDone} from './hoge'  // imported automatically

getThingsDone(hoge)

プリティーニート。素晴らしい。

カーソル下のタイプ定義を調べたい場合は、<kbd>shift-K</kbd>キーを押して表示するように設定しています。
<kbd>shift-K</kbd>を押下すると、ツールチップでタイプ定義を表示します。
なので型定義がなんだっけと忘れてしまっても、すぐに調べることが出来ます。

tooltip

更に、ツールチップの内容からはちょっとよく分からん、となった時、もっと詳細を確認したい時、
<kbd>gd</kbd>キーを入力します。これは'go to definition'という意味です。
すると、定義元へとジャンプしてくれます。
<kbd>ctrl-o</kbd>で前に戻ります。
同じファイル内の定義も、ちゃんとカーソルを定義元へと運んでくれます。

このように、coc.nvimはコーディングの補助を協力に行ってくれます。
とてもパワフルで便利です。おすすめ。

この例ではTypeScriptでデモンストレートしましたが、僕は基本的にFlowJSで書いています。
coc.nvimはFlowJSでもしっかり動作します。
たとえばInkdropのNoteモジュールがこちらにあります。
ご覧のように、まぁTypeScriptほどではないにしろ、上手く動きます。
オートコンプリーションも…まぁまぁですね。

FlowJS

ともあれ、便利です。無いよりマシ。
正直言うと、さっさとFlowJSからTypeScriptに移行したいのですが、コードベースが巨大なので難しいです。
しょうがないので今はFlowJSに甘んじています。

こちらがcoc.nvimの設定ファイルです。
重要なのはエクステンションです (.config/nvim/plugins/coc.rc.vim)。
4つのエクステンションを入れています:

" Extensions
let g:coc_global_extensions = [
  \ 'coc-json',
  \ 'coc-tsserver',
  \ 'coc-prettier',
  \ 'coc-eslint',
  \ ]

もしTypeScriptを書くのなら、coc-tsserverエクステンションを入れてください。
その他、json、prettier、eslintなどのヘルパー系エクステンションを入れています。

あとcoc-settings.jsonというもう一つ設定ファイルがあります:

{
  "coc.preferences.formatOnSaveFiletypes": ["json", "css", "markdown"],
  "eslint.autoFixOnSave": true,
  "eslint.autoFix": true,
  "tsserver.enableJavascript": false,
  "languageserver": {
    "flow": {
      "command": "flow",
      "args": ["lsp"],
      "filetypes": ["javascript", "javascriptreact"],
      "initializationOptions": {},
      "requireRootPattern": true,
      "settings": {},
      "rootPatterns": [".flowconfig"]
    }
  },
  ...
}

FlowJSを書くなら、Language Serverの設定をこのように書く必要があります。
FlowはLanguage Serverのプロトコルを喋りますので、それを使います。
TypeScriptとFlowJSを両方書くという方は、"tsserver.enableJavascript": falseを指定して、jsファイルを編集する時にTypeScriptが無効になるようにします。

That's it.

defx.nvim - ファイラ

僕は画面左にずっとファイルツリーを表示しているのが嫌いです。
なので必要な時に都度ファイラを立ち上げるようにしています。このように:

filer

そこから選んでファイルを開きます。
ファイラはdefx.nvimです。
<kbd>sf</kbd>キーで開くように割り当てています。

設定ファイルは以下のような感じです:

nnoremap <silent>sf :<C-u>Defx -listed -resume
      \ -columns=indent:mark:icon:icons:filename:git:size
      \ -buffer-name=tab`tabpagenr()`
      \ `expand('%:p:h')` -search=`expand('%:p')`<CR>
nnoremap <silent>fi :<C-u>Defx -new `expand('%:p:h')` -search=`expand('%:p')`<CR>

確かReadmeからコピペしてきました。
これでディレクトリやコンポーネント群をvimライクなキーバインドでエクスプロールできます。

もしファイルを変更した場合は、'M'というラベルが表示され、変更があることを教えてくれます。
非常に良く出来たファイラです。好き。

Modifier label

もちろん、ファイラ内でファイル操作も出来ます:

  • Create new file: <kbd>shift-N</kbd>
  • Delete file: <kbd>D</kbd>
  • Rename file: <kbd>R</kbd>

フォント

Nerd Fonts

上記スクリーンショットにて、JavaScriptやフォルダやその他の形式のアイコンが表示されていたことに気づいたかもしれません。
これはNerd Fontsを使っているからです。
このフォントはFont Awesome, Devicons, Weather IconsやSeti UIなどの沢山のアイコンが同梱されたフォントです。
これを使うことでターミナル上でアイコンを描画できます。

denite.nvim - ファイル検索

プロジェクト内でファイル検索するときは、denite.nvimを使います。
このプラグイン自体は検索の機能を提供しませんが、僕はそう出来るように設定しています。
設定はこちらにあります。

例えば、Inkdropプロジェクトでは沢山のファイルがあります。
それらをファイル名で検索するには、 <kbd>;f</kbd>と入力します。すると検索ウインドウが表示されます。

file search

ここで 'editor' のようにキーワードを入力すると、それにマッチするファイル群がフィルタされて表示してくれます。
なのですばやくファイルを見つけて開けます。

ファイルの中身をgrepして探したい場合は <kbd>;r</kbd>を押します。
'Notebook'のようにキーワードを入力すると、すぐさまキーワードが出現する場所とファイルを一覧表示してくれます。

Grep files

その上、この検索結果からさらにキーワードを入力して絞り込めます。
なので沢山のファイルがあなたのプロジェクトにあっても、素早く探し出すことが出来ます。

denite.nvimの設定は複雑なので全部説明するのは難しいです。
こちらがキーマップの設定です:

nnoremap <silent> ;r :<C-u>Dgrep<CR>
nnoremap <silent> ;f :<C-u>Denite file/rec<CR>

Dgrepコマンドは以下のように定義しています:

" Ag command on grep source
call denite#custom#var('grep', 'command', ['ag'])
call denite#custom#var('grep', 'default_opts', ['-i', '--vimgrep'])
call denite#custom#var('grep', 'recursive_opts', [])
call denite#custom#var('grep', 'pattern_opt', [])
call denite#custom#var('grep', 'separator', ['--'])
call denite#custom#var('grep', 'final_opts', [])
" grep
command! -nargs=? Dgrep call s:Dgrep(<f-args>)
function s:Dgrep(...)
  if a:0 > 0
    execute(':Denite -buffer-name=grep-buffer-denite grep -path='.a:1)
  else
    let l:path = expand('%:p:h')
    if has_key(defx#get_candidate(), 'action__path')
      let l:path = fnamemodify(defx#get_candidate()['action__path'], ':p:h')
    endif
    execute(':Denite -buffer-name=grep-buffer-denite -no-empty '.join(s:denite_option_array, ' ').' grep -path='.l:path)
  endif
endfunction

実際にやっているのは、ag という外部プログラムを呼び出しています。
これはスピードにフォーカスしたコード検索ツールです。
それで、このコマンドにコンテキストに応じたパラメータを付与して呼び出して検索しているという訳です。
プリティーニート。

auto-pairs

あとは細かい部分についてですが、auto-pairsというプラグインを好んで使っています。
これは、名前の示すとおり、ブラケット(かっこ)のペアの入力補助をしてくれます。
開きカッコを入力したら、自動で閉じカッコも入力してくれるというやつです。
ダブルクォートでも動きます。片方を消すと、もう片方のクォートも消してくれます。
シングルクォート''、スクエアブラケット[]、カーリーブラケット{}、ふつうのブラケット()などで動作します。
よく入力する文字なので助かっています。


That's pretty much it!
あなたのターミナル上でのワークフロー改善の参考になれば幸いです。

ブログの更新通知をメールで受け取る

My YouTube channel

Discussion