我很喜欢 LocatorJS 的那个瞬间。

在页面上按住 Option 或 Alt,点一下元素,编辑器直接跳到对应源码。没有先开 DevTools,再查组件名,再全局搜索。眼睛看到哪里,代码就打开到哪里。

这个体验用起来像一个小魔法,接入它却不该要求每个项目都重新研究魔法背后的机关。源码插桩、浏览器运行时、框架适配器和编译配置,如果全部摊在应用里,一次定位就会变成一份集成说明书。

所以我做了 vite-plugin-locator,想把应用侧的入口压到一行。

plugins: [locator(), react()]

一行不是因为底下没有复杂度,而是这份复杂度应该由一个地方接住。

点一下元素,背后其实有两条链路

浏览器能够从一个 DOM 元素回到源码,至少要完成两件事。构建阶段先给 JSX 或 TSX 节点附加文件与位置,运行阶段再识别用户点中的元素,选择合适的框架适配器,把那份位置交给编辑器协议。

只有运行时,页面里没有可用的源码元数据;只有编译转换,浏览器又不知道什么时候该响应点击。两条链路缺一条,功能看起来都像装好了,真正按下快捷键时却什么也不会发生。

这也是我对「集成」这个词的理解。把几个依赖列进安装命令不算集成,能够让它们共享同一份数据契约,并在失败时给出可以理解的边界,才算。

插件顺序不是文档习惯,而是行为契约

locator() 需要放在 React 等框架插件之前。源码位置最好在框架转换发生之前写入;等 JSX 已经被 Oxc、Babel 或其他编译器改写,再尝试还原组件边界,结果会脆弱得多。

我不想靠修改各个框架插件的私有配置来维持这件事。私有实现今天是 Babel,明天可以切到 Oxc,应用不应该跟着重写定位配置。插件选择站在 Vite 的公共 transform 钩子上,让底层编译器变化尽量停在插件内部。

这里有一条我很在意的原则。兼容性如果建立在「猜另一个插件现在怎么实现」上,迟早会变成追着上游跑;建立在公开扩展点上,才有机会成为真正的承诺。

开发工具最该证明的,是它没有进入生产包

源码路径、编辑器跳转协议和检查浮层都只属于开发环境。它们跑进生产包,不只是多了一点体积,还可能把本地目录信息一起带出去。

插件用 apply: 'serve' 限制开发期集成,但我不愿意只看配置就宣布隔离成功。配置表达的是意图,生产构建产物才是结果。验证时应该真正执行构建,再扫描输出里有没有 LocatorJS 运行时、源码位置标记或其他开发残留。

「代码写了只在开发启用」和「公开产物里确实没有」不是同一句结论。

前者是设计,后者才是证据。

auto 模式可以省配置,不能抹平框架差异

framework: 'auto' 的目标,是让常见 JSX 与 TSX 项目不用再重复填写框架名,并在浏览器阶段为 React、Preact、Solid、Vue 或 Svelte 选择适配器。

但自动检测不能为了支持列表好看,就假装每个框架能提供完全一样的位置。某些 Vue 场景只能落到组件第一行,SSR 可能识别不了,Svelte 的组件名和边界也不一定完整。这些限制应该直接出现在文档里。

当自动判断有歧义时,调用者仍然可以显式选择框架。好的默认值负责减少重复配置,不负责剥夺纠错能力。

我宁愿支持列表后面带着几条诚实注释,也不想让用户在点击无反应以后才发现「支持」其实只代表依赖能安装。

跨版本支持,最终要在真实组合里发生

一份 TypeScript 类型检查无法证明多个 Vite 主版本都能工作。真正容易出问题的是 Node.js 版本、框架插件组合、转换顺序、浏览器交互和最终打包内容。

因此兼容验证要走完整路径。各个 Vite 主版本的最小项目能够启动;按键点击能拿到正确源码位置;生产构建没有开发标记;npm tarball 包含声明的入口与类型文件。发布以后,还要确认 npm 包、Git tag 和远端提交确实属于同一次发布。

这些检查看起来和「一行配置」很不相称。

其实正好相反。

调用侧越简单,维护侧越需要认真。locator() 这一行不是魔法咒语,而是一份责任声明。它承诺版本差异、运行时注入和生产隔离不会被摊回每个业务仓库。

我喜欢的开发体验,大多都是这样来的。不是复杂度消失了,而是有人把它接过去,并且一直没有松手。