nvim-lspconfig 中 Vue 语言配置:vue_ls 与 vtsls 协同的完整指南
【免费下载链接】nvim-lspconfigQuickstart configs for Nvim LSP项目地址: https://gitcode.com/GitHub_Trending/nv/nvim-lspconfig
在 Neovim 里打开一个.vue文件,模板里的样式提示正常,<script setup>里的 TypeScript 却没有任何补全——这是 nvim-lspconfig 用户用 Vue 项目时最常见的"半残"状态。本文讲清两件事:nvim-lspconfig 的vue_ls为什么必须和一个 TypeScript 服务器搭档工作,以及从安装到排错的完整落地路径。你可以直接拿到可复制的配置、每个关键字段的作用说明,和 4 个高频问题的自检方法。
一个服务器管不了整个 .vue 文件
Vue 单文件组件(SFC)里同时住着三类代码:<template>的 HTML、<style>的 CSS、<script>的 TypeScript。早期方案(volar 的 takeover 模式)让一个服务器接管整份文件的所有请求,简单但职责边界模糊。Vue 语言服务器 v3.0.0 起移除了 takeover 模式,改为"混合模式":
| 旧做法(takeover 模式) | 新做法(混合模式,v3.0.0 起) | |
|---|---|---|
| 架构 | 单一服务器处理模板、样式、脚本全部请求 | vue_ls只管 CSS/HTML 部分,TS 请求转发给 tsserver |
| TypeScript 能力 | 内嵌,能力跟随语言服务器版本 | 由vtsls(或 Neovim 内置ts_ls)提供,独立升级 |
| 配置成本 | 一处配置 | 两个服务器 + 一个 tsserver 插件 |
| 多语言混用 | 容易和独立 TS 服务器冲突 | 天然与 JS/TS 生态共存 |
理解这一点后,后面的配置都是顺理成章的:vue_ls遇到 TypeScript 请求时,自己不会处理,而是转发给 tsserver 通道。仓库里 lsp/vue_ls.lua 的文件头注释明确写着:需要vtsls配合@vue/typescript-plugin才能覆盖.vue文件里的 TypeScript。
从安装到跑通:三步完成配置
第一步:安装语言服务器
用 npm 全局安装两个包,Neovim 侧不需要额外依赖:
# vue_ls 的启动命令是 vue-language-server,vtsls 的是 vtsls npm install -g @vue/language-server @vtsls/language-server第二步:先给一个最小可运行版本
最小配置只需 2 行,先让它跑起来:
-- 两个服务器缺一不可:vue_ls 管模板/样式,vtsls 管 TS vim.lsp.enable('vue_ls') vim.lsp.enable('vtsls')此时打开.vue文件,<template>的补全和<script>的基础 TS 能力应该都能用。如果<script setup>里项目类型、组合式 API 的提示还是残缺,继续第三步。
第三步:完整版,接入 @vue/typescript-plugin
让 tsserver 认识 Vue 单文件组件,需要注册一个全局插件:
-- @vue/typescript-plugin 的宿主包路径。 -- 用 mason.nvim 安装时,Mason 默认把包装在 data 目录下 local vue_language_server_path = vim.fn.stdpath('data') .. '/mason/packages/vue-language-server/node_modules/@vue/language-server' local vue_plugin = { name = '@vue/typescript-plugin', -- tsserver 按这个名字加载插件 location = vue_language_server_path, -- 必须是包的真实路径 languages = { 'vue' }, -- 关键:即使 filetypes 里有 vue 也要写这里 configNamespace = 'typescript', } vim.lsp.config('vtsls', { settings = { vtsls = { tsserver = { globalPlugins = { vue_plugin }, -- 插件在此注入 tsserver }, }, }, -- 默认只覆盖 js/ts 四种类型,补上 vue 才能接管 SFC filetypes = { 'typescript', 'javascript', 'javascriptreact', 'typescriptreact', 'vue' }, }) vim.lsp.enable('vue_ls') vim.lsp.enable('vtsls')如果你没有用 mason,而是npm install -g,把vue_language_server_path改成@vue/language-server在 npm 全局目录下的实际路径即可(可用npm root -g查到前缀)。
拆开看内部:on_init 的转发逻辑
真正让这套配置"自动工作"的,是 lsp/vue_ls.lua 里的on_init钩子。逐字段拆解:
cmd = { 'vue-language-server', '--stdio' }:启动命令。作用是用 stdio 模式拉起官方语言服务器;一般不用改,除非你用了自定义安装路径,此时才需要补全可执行文件位置。filetypes = { 'vue' }、root_markers = { 'package.json' }:前者决定哪个文件触发服务器,后者决定它把项目根目录识别为哪里。monorepo 里根目录识别错了会导致它找不到正确的vue版本。on_init里的typescriptHandler:核心逻辑。它监听tsserver/request通知,然后依次查找当前缓冲区上的ts_ls、vtsls、typescript-tools三个候选客户端,找到后调用typescript.tsserverRequest命令把请求转过去,再把结果通过tsserver/response回传给vue_ls。这里有个值得注意的细节:Neovim 0.11 内置的ts_ls排在第一位,如果你环境里已有内置 TS 服务器,vue_ls会优先转发给它。- 找不到任何 TS 客户端时的兜底:重试 10 次(每次间隔 100ms),仍失败则弹出
Could not find ts_ls, vtsls, or typescript-tools lsp client...错误。这条提示本身就是最重要的排查线索。
对比 lsp/vtsls.lua 的root_dir函数:它用package-lock.json、yarn.lock、pnpm-lock.yaml、bun.lockb等锁文件定位项目根,并主动排除 Deno 项目(检测到deno.json或更近的deno.lock就直接放弃)。所以 vtsls 和 ts_ls 二选一即可,不要同时启用两个。
高频问题速查
现象一:<script setup>里补全残缺或报错
- 根因:
@vue/typescript-plugin没有注册到 tsserver,典型是globalPlugins漏配、location路径错误,或languages里少了'vue'。 - 解法:核对第三步配置。自检:
:checkhealth vtsls确认服务器健康,再在.vue文件里跑:lua =vim.lsp.get_clients({ bufnr = 0, name = 'vtsls' }),应返回一个客户端对象而非空表。
现象二:弹出 "Could not find ts_ls, vtsls, or typescript-tools"
- 根因:
vue_ls已启动,但缓冲区上没有任何 TypeScript 客户端。常见于只启用了vue_ls忘了vtsls,或 vtsls 因根目录判定失败没有附着。 - 解法:确认
vim.lsp.enable('vtsls')存在;项目根要有package.json和至少一种锁文件(vtsls 的root_dir靠它定位)。自检命令同上,另跑:checkhealth vtsls看 "Server is active" 状态。
现象三:服务器压根没启动
- 根因:npm 包没装或不在 PATH 里,
vue-language-server/vtsls可执行文件找不到。 - 解法:终端里跑
which vue-language-server && which vtsls;在 Neovim 里等价检查:
print(vim.fn.exepath('vue-language-server')) -- 非空路径才算装上 print(vim.fn.exepath('vtsls'))现象四:Vue 2 项目行为异常
- 根因:
vue_ls默认只支持 Vue 3 项目(见 lsp/vue_ls.lua 文件头注释),Vue 2 项目需要按官方 language-tools 文档做额外配置,核心是让语言服务器找到项目内的 Vue 2 编译器依赖。 - 解法:先确认项目里装的是
vue@2.x::lua =vim.fn.system('node -p "require(\'vue/package.json\').version"')。版本对但行为异常时,对照官方 README 的 Vue 2 章节补齐项目侧依赖,而不是改 Neovim 配置。
进阶:性能与工程化
以下两条针对多项目或大型仓库场景,小项目可跳过。
- monorepo 零额外配置:vtsls 本身支持 monorepo,会为每个包自动找到对应的
tsconfig.json/jsconfig.json,无需为每个包起一个服务器实例。建议把统一版本的 TypeScript 放在 workspace 根,让 vtsls 只解析一次 TS 二进制路径。 - 按需加载(可选):只在打开 Vue 相关缓冲区时启用服务器,减少纯 TS/JS 项目里无关进程的占用:
vim.api.nvim_create_autocmd('FileType', { pattern = { 'vue', 'typescript', 'javascript', 'typescriptreact', 'javascriptreact' }, callback = function() if not vim.lsp.get_clients({ bufnr = 0, name = 'vtsls' })[1] then vim.lsp.enable('vtsls') if vim.bo.filetype == 'vue' then vim.lsp.enable('vue_ls') end end end, })- 管理方式二选一:mason.nvim 适合统一管理路径(上文的
stdpath('data')写法就是配合它),npm 全局安装则更直接。两者混用时注意location指向要和实际安装位置一致,否则插件静默失效。
小结
回顾主线:Vue 3.0.0 起语言服务器不再"包打天下",vue_ls负责模板与样式,TypeScript 能力通过on_init里的转发机制交给vtsls(或内置ts_ls);配置上的全部工作量,就是装好两个包、注册@vue/typescript-plugin、并保证languages里有'vue'。出问题先看:checkhealth vtsls和缓冲区客户端列表,80% 的故障在这一步现形。
延伸阅读:doc/configs.md 中vue_ls与vtsls的完整条目,以及 lsp/vue_ls.lua、lsp/vtsls.lua 的源码注释。如果你的项目里还有别的语言组合(比如 Nuxt + Solidity 这种混搭),转发机制是怎么配合的,欢迎在讨论区留言交流你的配置。
【免费下载链接】nvim-lspconfigQuickstart configs for Nvim LSP项目地址: https://gitcode.com/GitHub_Trending/nv/nvim-lspconfig
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考