ttypescript plugin 入门指南
作者:阳羡
本文为了快速说清楚相关原理,可能表述不够严谨,希望深入学习的同学请以源码为准
ttypescript 是什么
首先这并不是一个 typo。
在我们的业务逻辑中,总有需要对代码进行处理的需求,大部分情况下我们都可以使用 babel,但在少数情况,比如更加轻量的场景,可能我们更想使用 typescript。
而仅依靠 tsconfig,tsc 不支持插件系统。不过,typescript 的 API 却有针对 plugin 的支持。于是,万能的社区就开发出了 ttypescript 这个库。ttypescript 的意思是 transformer typescript,意为可以使用转换器的 typescript。在本文中,transformer 与 plugin 是同一个意思,后文将使用 plugin。
理解 ttypescript
理解 ttypescript 可以帮助我们更好地编写 plugin,ttypescript 的逻辑非常简单。ts 中有如下的函数:
重要的是最后一个参数:customTransformers
,这个参数就是我们所说的 plugin。
ttypescript 所做的,就是读取 tsconfig 中的 compilerOptions.plugins
,并把它传递给上文的函数,非常的简单易懂,没有花里胡哨的魔法。
CustomTransformers
的定义如下:
before 会在 ts 生成 js 之前调用,after 会在生成 js 之后调用,afterDeclarations 会在生成 dts 后调用。其中,最常用的是 before。
需要注意的是,对于每个文件,ts 都会调用上述函数。
TransformerFactory
的定义为:
context
中比较重要的字段为 factory
,这个字段上包含了一系列的 createXxx
这样创建结点的工厂函数:
有同学会有疑问,typescript 本身就有 ts.createXxx
的 API,为啥又提供一个context.factory
呢?我也不清楚,欢迎有兴趣的同学深入研究。
不过,需要强调的是,typescript 要求调用方在创建 语法树 结点时应该使用 context.factory.createXxx
而不是ts.createXxx
至于 Transformer
,它的含义是接受原有的节点,返回更新后的节点。
需要注意的是,插件仅会使用根节点,通常是 ts.SourceFile
,来调用此函数,需要开发者自己使用 ts.visitEachChild
或ts.visitNode
这样的辅助函数去遍历节点
我猜测,这样能够避免很多无效遍历,是为了更好的性能优化。
分析完了 plugin 的类型,让我们再来回过头看 ttypescript 对 tsconfig 的要求。
tsconfig 中新增compilerOptions.plugins
字段,其类型为 PluginConfig[]
除此之外的其他参数会作为 options 传递给 plugin。
transform
是需要加载的插件,指定不同的type
,ttypescript 就会给插件额外传入不同的参数。
举例来说,有如下的 tsconfig:
其中,ts-import-plugin
是 plugin 的包名,ttypescript 会去 require
这个包。
libraryName
等不属于PluginConfig
的字段是 plugin 的 options,会用于 plugin 初始化。
config
是 plugin 的类型,常见的是 program
与 config
,默认值是program
当type
为 program
时,要求 plugin 类型如下:
当 type
为 config
时,要求 plugin 类型如下:
如果返回值是函数时,默认作为 before
,PluginConfig
中的 after
与 afterDeclarations
可以覆盖此设置。
对于复杂情况,返回值也可以是 ts.CustomTransformers
编写一个 ttypescript plugin
学了这么多理论,让我们通过一个实际例子来巩固一下学习成果。
我们的目标是把import 'styles.less'
变为import 'styles.css'
第一步,我们需要搭一个插件的架子
这就已经是一个合格的插件了,不过这个插件什么事都没有做。
第二步,我们需要把import
的包打印一下,看看是否能正确识别import
语句。
把以下代码放在上文核心代码注释那里:
关于读取 AST 对应的文字,可以试试text
或者getText()
,不一定都能跑,但是有一个能跑就行。
假设运行结果如下:
这样的运行结果证明了我们这一步是能够成功运行的,不过我们只需要处理 less,而不用处理其他导入。
第三步就是处理导入,把上文console.log
换为下方代码
运行代码,发现import 'styles.less'
被成功替换为import 'styles.css'
,代表我们的插件已经写完啦
小练习:能不能对上面的插件做一些性能优化,避免无效的遍历?
总结
ttypescript 通过读取 tsconfig,并把 plugin 的配置传递给 typescript,使我们可以编写 plugin 来针对 ts 代码做额外处理,是除了 babel 以外的另一个选项。
转载自:https://juejin.cn/post/7127849917317382151