🧊

NeovimのNative LSP+Mason環境におけるVue3のサポート(ついでにmonorepoでも)

タイトルはもう面倒になった気持ちの表れ・・・。

今度の副業案件はVueということで、そのセットアップをしてみたが、なんかシュッといかず大変だったので、そのポイントをメモしておく。

最初に書いておくと、Svelteの場合は同様のプロジェクト構成でも困ったことないので、そういうとこやぞって思いながらやってた。

前提

  • VSCodeではなくNeovim
  • COCではなくNative LSP
  • LSPのインストールはMason

まあそれなりにメジャーなセットアップなのではなかろうか。

この状態で、

  • NuxtではなくVue 3単体
  • SFCで<script setup lang="ts">なコンポーネントを書き
  • Viteでビルドする

っていう定番プロジェクトを、快適なDXではじめたかっただけなのに・・・という話。

LSPはVolar

いろんな経緯があったらしいが、どうやら最新のLSPはVolarというプロジェクトらしい。

volarjs/volar.js: 💙🌊 https://github.com/volarjs/volar.js

というより、VueのLSPまわりのツール群が、このVolar.jsをベースにしてるという表現が正しいとのこと。Volar.js自体は、もっとスコープの大きいやつっぽい。

vuejs/language-tools: ⚡ High-performance Vue language tooling based-on Volar.js https://github.com/vuejs/language-tools

Vue2の時代では、Veturというプロジェクトがその座にいたけど、Vue3ではLegacyになったらしい。

というわけで、VolarをLSPとしてインストールすればよい。

Masonを使ってる場合は、vue-language-serverってのがそれ。vなんたらって名前のLSPはいっぱいあってとても紛らわしいので注意。

ただこのVolarをインストールしたら、あとはmason-lsp-configがいい感じにしてくれて終わり・・・とはいかない。

厳密には、.vueファイルではそれっぽい動作になるけど、.js.tsファイルから.vueimportするところで拡張子を解決できずエラーになる。

というのも、LSPとしてvolarが動くのは、filetype: vueのときだけ。 なので、(java|type)scriptなときにはtsserverが動いてしまって、それでエラーになってる。

まあそんなに困ることはないので、これでも良いって判断もあるかもしれない。

.vueファイルを.tsからimportできるように

これを解決するためには、tsserver.vueを認識させる必要がある。

が、ドキュメントには、VSCode向けのプラグインがあるよとだけ書かれてる。

TypeScript Vue Plugin (Volar) - Visual Studio Marketplace https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin

(いやいや他のエディタは?)

なんでこんな表現をしてるのかはわからんけど、個別にインストールしてtsconfig.jsonにプラグインを追加すれば使えた。

  "compilerOptions": {
    "plugins": [
      { "name": "typescript-vue-plugin" }
    ]
  }

これにて一件落着・・・かと思われた、が。

Volar Takeover mode

ただこのプラグイン、なんか注意書きがしてあり。

⚠️ It’s recommended to use take over mode instead of VSCode built-in TS plugin.

Takeoverモードっていうのは、

  • volar.vue向けにTSのサーバーを起動する一方
  • tsserver.ts向けに別のインスタンスを起動する

という無駄を解消できるモードとのこと。つまりはvolarで全部やるぜモードってことらしい。

Using Vue with TypeScript | Vue.js https://vuejs.org/guide/typescript/overview.html#volar-takeover-mode

推奨するならやっておくかということで、NeovimのLSPでこのモードを使うには、

require("mason-lspconfig").setup_handlers({
  function(server)
    local options = {}

    if server == "volar" then
      options.filetypes = {
        "vue",
        "typescript",
        "javascript",
      }
    end

    require("lspconfig")[server].setup(options)
  end
})

というようにfiletypesをいじるだけ。

ただこれと同時に、tsserverを無効化しておく必要があって、こっちが大変。(さもないと結局2重に起動しちゃうから)

なんせその.tsファイルが、Vueのプロジェクトにおける.tsファイルか?を判断しないといけないが、それをどうやる?VSCodeはワークスペースごとに設定の余地があるのでそこでtoggleできるらしいけど。

いくつかパターンを考えてみたけど・・・、

  • typescript-language-serverをアンインストールしてしまう
  • tsserverfiletypesからtypescriptを消す
  • folke/neoconf.nvimをいれる
  • Takeoverモードを諦める

どれもぱっとしない。

neoconf.nvimはクリーンな案に見えるけど、なんでこのためだけに依存を増やして設定書いてファイルをコミットしないといけない・・・?ってのが正直な気持ち。

Using Volar’s Takeover mode in Neovim’s Native LSP Client https://theosteiner.de/using-volars-takeover-mode-in-neovims-native-lsp-client

.tsファイルで.vueimportしたいのなんて、正直マウントする瞬間くらいなので、すっぱり諦めるのも妥当だと個人的には思う。

ちなみにこのTakeoverモードは、巨大なプロジェクトになると欲しくなってくるらしく、そうならないように祈るばかりである。

モノレポでエラー

まだ終わらない。

ここまでの設定では、モノレポで次のようなエラーが出る。

RPC[Error] code_name = InternalError, message = “Request initialize failed with message: Can’t find typescript.js or tsserverlibrary.js

VolarがTypeScriptのLSを立てようとして、見つけられなくて、エラーになってる。

どうやらローカルのpackage.jsonの隣のnode_modulesしか探してくれない(どのレイヤーの問題?)らしく、モノレポみたくそこにnode_modulesがない場合に困る。

で、それを解決するには、パスを自分で指定するか、関数を書くしかないらしい。

volar (Vue) - nvim-lspconfig https://nvim-lsp.github.io/configurations/volar/

いやいや面倒すぎる・・・!となったので、怠惰な私はシンボリックリンクを貼って済ませた。

ln -s ../../node_modules/ node_modules

とりあえず動いてるし、モチベーションも無なので、これでなんか問題が出たらその時に対処しようと思う。

まとめ

  • Masonでvue-language-serverを入れるのは必須
    • .vueファイル内さえそれっぽく動けばいいなら、これで終わり
  • モノレポの場合は、なんらかの方法でパスを通す必要がある
  • 推奨のTakeoverモードを使いたいなら、なんらかの方法で.tsに対するtsserverを無効に
  • Takeoverモードを使用しないなら、typescript-vue-pluginで済ませてもいい
    • .ts.vueを解決できなくてもいいならこれも不要

正直なところ、印象はよくないな〜。

これだけやってもまだ他のフレームワークのTSサポートより弱いし・・。