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 类型如下:

如果返回值是函数时,默认作为 beforePluginConfig 中的 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 以外的另一个选项。

作者:豆皮范儿

%s 个评论

要回复文章请先登录注册