ByteNoteByteNote
编辑器报红时,@ 还没进类型检查
字

字节笔记本

2026年10月7日 · 约 12 分钟读完

编辑器报红时,@ 还没进类型检查

API中转
¥120

编辑器里 import x from '@/components/layout/MainLayout' 画红线,开发服务器却不一定报同样的错。Vite 的 resolve.alias 只告诉打包器 @ 指到 ./src。类型检查看的是 tsconfig 的 paths。两套都写上,而且写在真正包含源码的那份配置里,红线才会消失。

打包别名和类型检查路径是两套

根配置上的 paths 没作用到源码

对话里的 vite.config.ts 把 @ 指到 path.resolve(__dirname, './src')。同时还有一个开发服务器代理:/api 转到 http://localhost:8000,changeOrigin 打开,rewrite 把路径开头的 /api 拿掉。代理只影响请求转发,和 @ 能不能被编辑器认识无关。

同一份对话里的根 tsconfig.json 是解决方案风格:"files": [],再用 references 指向 tsconfig.app.json 和 tsconfig.node.json。paths 写在这份根文件上,有两条:@/* 对 ./src/*,@/pages/* 对 ./pages/*。files 为空意味着这份配置自己不包含任何源码。引用项目用的是自己那份 compilerOptions。路径映射留在根上,编辑器检查 src 里的文件时用不上。

所以现象是:Vite 能把 @/components/... 解析进 src,编辑器仍报找不到模块。重启开发服务器改变不了语言服务读哪份 tsconfig。

两条 paths 会把同一导入送去两个目录

打包器只配置了 @ 这一个别名,没有单独的 @/pages。@/pages/Dashboard 在 Vite 里就是 src/pages/Dashboard。

类型检查若真的用上根上的两条规则,@/pages/* 会把同一个导入送到项目根下的 ./pages/*。一个导入,构建去 src/pages,编辑器去 pages。页面文件只放在其中一处时,另一边永远是红的,或者构建能过、类型过不了。

对话里的改法是把映射收成一条:@/* 对 ./src/*。页面放在 src/pages,导入写成 @/pages/Dashboard,和 @/components/layout/MainLayout 一样,都落在 src 下面。baseUrl 设为 "."。这条 paths 要写进实际 include 了 src 的那份配置。根文件继续当解决方案入口时,就在 tsconfig.app.json 里写同样的映射,而不是只写在 "files": [] 的根上。

vite.config.ts 自己的类型由 tsconfig.node.json 负责。对话里给它的 include 是 vite.config.ts,compilerOptions 里有 composite、moduleResolution: bundler。这份文件不用再抄一份 @/*,它不负责 src 里的导入。

语言服务要重新读配置

配置保存之后,VS Code 里用命令面板执行 TypeScript: Restart TS Server。语言服务会缓存旧的 paths。文件已经改对,红线还在,多半是这一步没做。

仍然不行时,对话里的下一步是删掉 node_modules/.vite,重新安装依赖,再重启开发服务器。这清的是预构建缓存。它解决的是运行时解析到旧文件,不是编辑器红线本身。两件事分开做:红线先看 paths 在哪一份 tsconfig,运行时报错再清 Vite 缓存。

导入保持从 src 起算:

ts
import { MainLayout } from '@/components/layout/MainLayout'
import { Dashboard } from '@/pages/Dashboard'

不要在别名已经指向 src 之后再写 @/src/pages。那样会拼成 src/src/pages。相对路径 ../../ 可以和别名并存,同一文件里混用两种,后面移动目录时只改了一半。

别名只保留一条

@ 在 Vite 里指到 src,在包含 src 的 tsconfig 里用 @/* 指到 ./src/*。根配置如果 files 为空,路径映射不要只放在根上。@/pages 不要再单独指到 src 外面的目录,否则和打包别名不是同一个文件夹。改完重启 TS Server。Vite 缓存是另一条线,红线还在时先别从删缓存开始。

同一导入不要被送到两个目录

对照两份配置逐项看

把对话里贴出来的文件对上。Vite 侧:插件是 @vitejs/plugin-react,别名表只有 '@'。server.port 是 3000。代理的键是 '/api',target 是 http://localhost:8000。rewrite 收到的 path 用正则 /^\/api/ 换成空串。请求 /api/users 转到后端时路径是 /users。这个函数的参数名叫 path,和文件顶上 import 的 Node path 不是同一个绑定,它只活在箭头函数里。别把别名的 path.resolve 写进 rewrite。

TypeScript 侧:根文件 baseUrl 是 ".",paths 有两条,files 是空数组,references 两条分别指向 ./tsconfig.app.json 和 ./tsconfig.node.json。空的 files 加上 references,这是解决方案文件。编辑器打开 src 里的 .tsx 时,用的是 app 那一份配置。只改根上的 paths,语言服务可以仍然说找不到模块。

对话里建议的完整 compilerOptions 还包含 moduleResolution: bundler、jsx: react-jsx、noEmit: true、strict: true。这些是 Vite 模板里常见的选项,和别名同时成立。缺 baseUrl 时,paths 不会按相对项目根来解释。两条都要在。

include 在那份建议里是 ["src"]。若页面其实在仓库根的 pages,这条 include 根本不包含它们,@/pages 指向 ./pages/* 也没用,因为那些文件不在本次检查范围内。目录先定成对话里画的那样:src/components、src/pages、src/routes,配置文件 tsconfig.json、tsconfig.node.json、vite.config.ts 放在项目根。然后再谈别名。

红线和运行时分开排

改完 paths 仍报红,执行 TypeScript: Restart TS Server。这一步不编译代码,只让语言服务重读配置。开发服务器没重启时,浏览器里的新别名可能还是旧的,那是 Vite 的问题,看终端而不是看红线。

对话里的最后三步是删除 node_modules/.vite、重新安装依赖、重启开发服务器。缓存目录里有预构建结果。别名指向的文件换了位置,旧缓存可能仍解析到原来的路径。这三步放在 paths 已经和 resolve.alias 对齐之后。顺序反了,会在缓存上耗时间,红线还在。

导入示例是 @/components/layout/MainLayout、@/pages/Dashboard、@/pages/Applications、@/pages/Settings。这些路径都默认文件在 src 下。如果磁盘上的页面在根目录 pages,要么把文件挪进 src/pages,要么给 Vite 再写一条别名,和 paths 写成同一个目录。只改其中一边,构建和编辑器仍会分裂。

代理和别名无关

server.proxy 把 /api 转到本机 8000 并去掉前缀,这只影响浏览器发出的接口请求。它不会让编辑器认识 @。红线消失的条件是:打包别名和 paths 指向同一棵 src,并且 paths 写在真正检查这些文件的那份 tsconfig 里。

还可以用一次导入做验收。在 src/routes 里写 import { Dashboard } from '@/pages/Dashboard'。编辑器不再画红线,同时 vite 开发服务器能编译通过,才算两套配置对齐。只有一边过,就回到 resolve.alias 和 paths 的目录是不是都落在 src。@/pages/* 若仍指向仓库根的 ./pages,把这条从 paths 删掉,页面文件挪到 src/pages,与对话里建议的目录一致。

tsconfig.node.json 的 include 只放 vite.config.ts。不要把 src 加进这份 node 配置,否则同一份源码被两套编译选项检查,报错会重复,也更难看清红线来自哪一份。根上的 references 继续指向 app 和 node 两份。改的是 app 那份里的 paths,不是把解决方案文件改成一份大而全的 include。

代理保持原样即可。port 是 3000,/api 转到 localhost:8000 并去掉前缀。这和模块路径无关。验收别名时不要用接口请求是否到达后端来判断 @ 是否生效。

语言服务报的是找不到模块,终端报的是 Vite 解析失败,两句不要当成同一个错误去搜。前者改包含 src 的那份 paths,改完执行 Restart TS Server。后者核对 resolve.alias 的 path.resolve(__dirname, './src') 是否还指向现在的目录,再考虑删除 node_modules/.vite。根 tsconfig 的 files 仍保持空数组,让 references 继续把 app 和 node 分开。把 paths 只留在根上、指望引用项目自动继承,就是这次红线的来源。

moduleResolution 用 bundler,jsx 用 react-jsx,和 Vite 模板放在同一份检查 src 的配置里。noEmit 打开,因为产物交给 Vite,不交给 tsc 去写出文件。这些选项不代替 paths。没有 paths,选项再全,@/ 仍然是红的。有 paths 但写错文件,选项再全也一样。同一条导入在编辑器和开发服务器里都通过,别名才算配完。相对路径可以留着,但同一文件里不要一半用别名、一半用上级目录去写。

相关文章

分享: