拒绝能跑就行为AngularJS1x老项目注入现代开发体验葡萄城技术团队
作者:互联网
2026-03-23
我们团队正维护一个持续增长的AngularJS 1.x项目,面对框架升级的高昂成本,我们选择通过现代化工具提升开发体验。本文将分享我们如何通过VS Code插件解决模板开发的痛点。
面对技术债务时,与其等待完美解决方案,不如主动改善开发体验。正如这个插件所证明的,在现有架构下进行渐进式优化,往往比彻底重构更具实践价值。

先看效果,再聊技术。这个插件目前已经覆盖了日常开发中最高频的场景:
数据绑定的智能提示
这是最核心的能力。在 HTML 模板的 {{ }} 表达式和指令属性中,你可以获得:
- 自动补全 :输入
ctrl.后,自动列出控制器上的所有属性和方法,带有完整的类型信息 - 悬停类型提示:鼠标悬停在表达式上,显示其 TypeScript 类型
- 跳转到定义:Ctrl+Click 直接跳到 TypeScript 中对应的属性或方法定义
- 函数签名提示:调用方法时显示参数列表和类型

组件与指令
自定义 component 和 directive 的标签名、属性名都有自动补全和悬停提示,点击可以跳转到定义处。补全时还会自动插入必填属性。

模板表达式诊断
在 HTML 中写错了 AngularJS 表达式?不用等到运行时了------插件会实时标红并给出错误信息。

更多实用功能
Filter 的补全、提示和跳转:

ng-*内置指令补全:
templateUrl一键跳转到 HTML 文件:
通过 Controller/Service 名称跳转到实现文件:

搜索 component/directive 在哪些地方被使用:

依赖注入匹配校验:

inline-html 语法高亮:

功能看起来很自然,但背后的技术问题并不简单。核心挑战是:AngularJS 的 HTML 模板是纯字符串,没有任何类型信息,而我们的业务逻辑已经用 TypeScript 写了。如何把两者连接起来?
我并不是一开始就想好了所有方案,而是一步步被真实需求推着往前走的。下面按实际开发历程来讲。
第一步:先解决类型从哪来的问题
最初的目标很简单:在 HTML 模板的 {{ ctrl.userName }} 上提供自动补全和类型提示。表达式本身就是合法的 JS 属性访问,不需要特殊解析------但关键问题是,TypeScript 的类型信息怎么拿到?
这里有一个方案选型的思考过程。
方案一:自己启动一个 TypeScript 编译器
最直觉的想法是在扩展中直接调用 ts.createProgram(),自己建一个 TypeScript 程序实例来做类型分析。但很快就会发现几个严重问题:
- 内存翻倍:VS Code 已经通过内置的 tsserver 为项目维护了一整套 AST 和类型信息。再起一个等于把整个项目的类型图在内存里复制一份,对于大型项目这是不可接受的。
- 编辑不同步 :用户在编辑器里改了代码还没保存,
createProgram()只能读磁盘上的旧文件。tsserver 维护了内存中的编辑缓冲区,能实时反映用户的修改。 - 重复造轮子 :
tsconfig.json的解析、项目引用的处理、文件和增量编译......这些 tsserver 都已经做好了,自己搞一套成本很高。
方案二:利用 VS Code 已有的 API
VS Code 提供了 vscode.executeCompletionItemProvider、vscode.executeHoverProvider 等命令,可以请求内置 TypeScript 扩展返回补全和悬停信息。但问题是:
- 这些 API 只对
.ts/.js文件生效,对.html文件不会返回 TypeScript 的类型信息。 - 即使通过虚拟文档等技巧绕过文件类型限制,返回的也是展示层的数据(字符串形式的标签、文档),而不是结构化的类型对象。我需要的是
ts.Type,需要能调用.getApparentProperties()(获取所有属性)、.getCallSignatures()(获取函数签名)、.getNumberIndexType()(获取数组元素类型)等方法来逐层深入。
方案三(最终选择):写一个 TypeScript Server Plugin,"住进" tsserver 里面
TypeScript 提供了一个官方机制:TypeScript Server Plugin。通过这个机制,我可以把自己的代码注入到 tsserver 进程中运行,直接访问它内部的 Program 和 TypeChecker 对象------不需要额外的内存,天然与用户编辑同步。
// TypeScript Server Plugin 的入口function init(modules: { typescript: typeof import('typescript') }) { return { create(info: ts.server.PluginCreateInfo) { // info.project['program'] 就是 tsserver 当前维护的 Program // 通过它可以拿到 TypeChecker,进行任意类型查询 } };}
这是三个方案中唯一能同时满足"零额外内存"、"实时同步编辑"、"完整类型 API 访问"的方案。代价是------它运行在 tsserver 进程中,而我的 VS Code 扩展运行在扩展宿主进程中,两者之间没有直接的调用接口。
第二步:搭建跨进程通信的桥梁
选择了 TypeScript Server Plugin 方案后,新的难题来了:扩展和插件运行在两个完全隔离的进程中,怎么让它们对话?
这个问题困扰了我相当长时间。我研究了各种可能的方案,也看了其他插件是怎么做的:
方案一:走 tsserver 的标准协议? 行不通。VS Code 严格管控了与 tsserver 之间的通信------标准协议中没有为插件预留自定义请求/响应的通道,多余的数据无法添加和返回。官方提供了一个 configurePlugin API,但它是严格单向的:只能从扩展向插件传配置,插件无法返回任何数据。
方案二:参考 Volar(Vue 语言工具)的 Request Forwarding? Volar 借助 VS Code 内部的 typescript.tsserverRequest 命令,通过 Language Server 作为中间层转发请求。方案很优雅,但它依赖一个独立的 LSP 服务器层,对我的场景来说太重了。
方案三:在 TS Plugin 中启动一个 HTTP Server。 研究了日本开发者
相关标签:
相关推荐
专题
+ 收藏
+ 收藏
+ 收藏
+ 收藏
+ 收藏
最新数据
相关文章
Elasticsearch93新增bfloat16向量支持
解析OceanBase生态工具链之OAT_obd_OCP_obshell
贝叶斯不确定性引导的早停框架ESTune与OceanBase校企联合研究
杈炬ⅵ&浜哄ぇ閲戜粨閫傞厤瀹炴垬锛歋eaTunnel鍦ㄤ俊鍒涙暟鎹钩鍙颁腑鐨勫簲鐢ㄤ笌韪╁潙鎬荤粨
2026年1月中国数据库流行度排行榜:OB连冠领跑贺新元PolarDB跃居次席显锐气
社区译文解析FUD与真相MySQL是否真的被弃用了
英伟达重新规划AI推理加速布局 暂停Rubin CPU转攻Groq LPU
gpress v1.2.2 全新上线 Web3内容平台迎来更新
CMake 4.3.0 正式推出
短剧采用AI换脸技术使角色酷似明星 制作方与播出方构成侵权
AI精选
