Terser项目中与Typescript主要的不兼容点及改进方向
Terser源自Uglifyjs,其开发时间较早,从现在的ES6、Typescript的视角看过去,存在不少的问题,代码结构存在一些可优化点。
这篇文章中,我主要讲一下在我眼中Terser项目中的问题,及相应的改进方向。
问题
类定义松散,不利于添加类型
terser中的类定义是这样的过程:
- 先定义一个基本的类,只包含骨架和简单方法
- 在各个分散的文件中,通过修改prototype的方式,为相应的类添加方法
按这种方式,类在一开始定义的时候,不包含相应的属性和方法,而在import某个文件之后,通过修改prototype,又变成了拥有此属性和方法。
在Typescript中,如果直接声明此类具备相应的方法,则会报类型错误。而如果声明这些方法是可选方法,则在使用的时候需要做各种非空判断,比较麻烦。
改进方向
为了解决这个问题,我在为terser添加类型之前,先对terser进行了一次大的重构。从原来的同类方法定义在一个文件中,改成同一个类的方法定义在一个文件中。
代码不符合es6规范
terser的代码仓库中大量充斥着es5的代码,如很多对prototype的操作,这样的代码在Typescript中都会比较难以处理,带来大量的类型问题。
改进方向
将相应的es5代码先转换成可正常工作的es6代码。
大量的工厂方法
terser中有大量的工厂方法,如 DEFNODE
用来创建类,def_optimize
用来在各个类中添加optimize
方法。
DEFNODE
中基于用传参的方式,动态地定义各种函数、方法,在Typescript中基本无法处理。
这些工厂方法增大了添加类型的难度。
改进方向
将相应的工厂方法删除,换成正常的定义。
大量的 instanceOf
instanceOf在typescript中是一种type gurad,可以用来判断类型。但是它有一个缺点,就是必须将相应的class引入。
在terser中,存在着大量的根据子类的类型,决定父类中函数的具体行为的语句。这就导致了如下的问题:
- 若是使用 instanceOf,能正常推导类型,但是会带来大量的循环依赖问题。且将来转到Rust/C++等静态语言时,会比较难处理。
- 若是不使用instanceOf,换成别的方式实现。则没有类型推导,不方便将类型从any换成具体类型,否则会报一些类型不匹配的问题。
改进方向1
首先将instanceOf换成了一个通用的实例方法 isAst,解决循环依赖的问题。
如原来node instanceOf AST_Class
现在可以换成node?.isAst?.('AST_Class')
,这样就没必要引入AST_Class类型,也就不会有循环依赖了。
改进方向2
换成isAst可以解决循环依赖问题,但是没办法解决类型推导问题。原来 instanceOf能起到type guard的作用,现在的isAst方法没有了。
要解决这个问题,可以为isAst方法添加类型推导能力,即
isAst<T extends AST_Node> (type: string): this is T
但是这样还是会需要引入AST_Node
,一个替换方案是将所有的AST类的定义,抽取成接口:
1// types.ts 2interface IAST_Node {} 3interface IAST_Class extends IAST_Node {} 4 5// ast/class.ts 6import { IAST_Class } from '../types' 7class AST_Class implements IAST_Class { 8 xxx () {} 9} 10 11// ast/node.ts 12import { IAST_Node, IAST_Class } from '../types' 13class AST_Node implements IAST_Node { 14 foo () { 15 if (this.isAst<IAST_Class>('AST_Class')) { 16 this.xxx() 17 } 18 } 19}
如上代码所示,首先要在一个types.ts
中定义所有的ast相关类的定义,然后需要在基类中引入子类的类型,再通过手动的 type guard推导类型。
虽然能用,这样使用起来就比较繁琐了。
改进方向3
第3种改进方向,就是将基类中对 instanceOf 的调用,切换成子类的方法覆盖。
如原来有
1class AST_Node { 2 foo () { 3 if (this instanceof AST_Class) { 4 // code for AST_Class 5 } 6 // code for AST_Node 7 } 8} 9class AST_Class extends AST_Node {}
变成
1class AST_Node { 2 foo () { 3 // code for AST_Node 4 } 5} 6class AST_Class extends AST_Node { 7 foo () { 8 // code for AST_Class 9 } 10}
但是这种方法改起来比较困难,一方面很多判断条件在抽取成方法时不好描述,另一方面容易引入bug。
总结
为了使tsterser开发更加顺利,在为terser添加类型之前,首先要对其进行一次重构,具体来说,主要是将类定义从工厂方法中抽取出来,将动态添加的方法写成静态定义。
另外因为全部抽取之后,所有类都在一起,lib/ast.ts文件变得过大(10000+行代码),还需要进行拆分。