Visual Studio Code 最近将其内置的JavaScript大小减少了20%,节省了超过3.9 MB。这种减少不仅降低了下载和存储需求,而且由于在运行JavaScript之前需要扫描的源代码更少,因此也提高了启动速度。这个减小是通过一个新的构建步骤“名称混淆压缩”实现的,而非删除任何代码或进行重大重构。
workbench.js随时间变化大小,右侧两个下降点:VS Code 1.74第一大跌幅结果来源于混淆压缩私有属性,VS Code 1.80第二小跌幅来源于混淆压缩 export。
在这篇文章中作者具体介绍了背后的思路,主要是两个方面的优化。下边看看实现逻辑。
混淆压缩源码后 JavaScript 仍包含许多长标识符名称如 extensionIgnoredRecommendationsService。作者本来以为 esbuild 已经将这些标识符简化了,比如:
const someLongVariableName = 123;
console.log(someLongVariableName);
变为更短的:
const x = 123;
console.log(x);
由于JavaScript以源文本形式发布,减少标识符名称的长度实际上可以减小程序的大小。这种优化可能看起来有些荒谬,但在JavaScript世界里确实顶瓜瓜。
尽管esbuild实现了混淆功能,默认情况下只有当确定混淆不会改变代码行为时才对名称进行处理。因此,在实践中, esbuild只对局部变量名和参数名进行处理。
也就是说,esbuild这种保守策略意味着许多无法确认是否安全修改名称被忽略了。
怎么办呢?
作者团队最终利用TypeScript对混淆代码进行验证,正如TypeScript可以在常规代码中捕获未知的属性访问一样,TypeScript编译器能够捕获到一个属性已经被混淆但对它的引用没有正确更新的情况。
解决思路:可以将TypeScript源码进行混淆,然后使用被改变标识符名称的新TypeScript进行编译。这种方式会使得对于是否无意间破坏了代码有更高的把握。
同时,通过使用 TypeScript,可以真正找到所有私有属性(而不是仅以 _ 开始的属性),甚至还可以利用 TypeScript 的现有重命名功能来智能地重命名符号,而不会意外改变对象形状。
他们提出了新的大致工作流程:
使用 TypeScript's AST 针对每个在代码库中发现的私有或受保护属性:
如果该属性需要被修改:
通过寻找未使用过的符号名称计算出一个新名称
使用 TypeScript 生成所有引用该属性的重命名编辑
将所有重命名编辑应用于TypeScript源码
编译带有修改过名称的新编辑过的Typescript资源
结果大部分有效。
当然,也有一些例外需要处理:
当前类内唯一性并不能满足要求,在超类和子类之间也必须具备唯一性。根本原因是 TypeScripts 私有关键字只是一个编译时装饰器,并不能真正防止超级和子类访问私有属性。如果不小心处理,则可能导致重新命名时产生名称冲突(幸运地是 Typescript 将其报告为错误)。
在某些情况下, 子类公开继承自父类受保护权限,在很多例子里面都属于错误操作, 需要禁止此处进行混淆。
这样构建成功后,混淆私有属性后 VS Code 主要 workbench.js 文件大小从12.3MB降低至10.6MB , 减少近14%。这也带来5%加载速度提高,因为需要扫描文本量减少。
另一方面,其实provideWorkspaceTrustExtensionProposals等长名字,或者localize函数(用于UI显示字符串)明显还有改善空间。针对它的处理是:导出符号名称。只要导出仅供内部使用,就可以缩短它们而不改变代码行为。
最终,经过优化,总体上文件比没有进行名称压缩小了20%。在整个VS Code中,名称压缩从编译源码移除3.9MB JavaScript代码,这既降低了下载大小和安装大小,也使每次启动VS Code需要扫描JS代码量减少3.9MB。
(文/开源中国)