如何实现一个 TypeScript 的宏

核心提示如何实现一个 TypeScript 的宏这只是一篇记录自己折腾经历的日记。对大家可能没什么帮助。想法Babel 是一个优秀的玩具,我们可以在上面做很多有趣的事情,于是我们有了非常多的 Babel 的 plugin,后来大家黑魔法玩多了,就出

如何实现一个 Typescript 的宏这只是一篇记录自己折腾经历的日记。对大家可能没什么帮助。

想法Babel 是一个优秀的玩具,我们可以在上面做很多有趣的事情,于是我们有了非常多的 Babel 的 plugin,后来大家黑魔法玩多了,就出现了小伙伴说,我不想配置那么麻烦,就有小伙伴开始写了一个 babel-plugin-macros。

(注:我是在 Create-React-App 的更新日志中发现了它,然后就用它写了一些好玩的东西。)Typescript 也开放了 transformations 的 API,我们是不是也可以做一些类似的事情呢?查资料作为没有学过编译原理的小白,我肯定是先去找资料学习一发。那个时候,我还在用 create-react-app-typescript 写东西,它是使用 ts-loader 作为配置的,把它的文档仔仔细细看了一下,发现了 getCustomTransformers 的配置,这个配置还有相应的单元测试 ts-loader/test/comparison-tests/customTransformer at 401fc690ed78d9a6915d56a1e2b49dd5e32b69e6 · TypeStrong/ts-loader · GitHub 。

照着单元测试撸了一发,大体就知道了大概。

学到的知识我就不细讲了,详见:手把手教写 Typescript Transformer Plugin - 知乎 毕竟东西都差不多。感觉还没过瘾,还是有点虚,就去看了一下 babel-plugin-macros 的源码和这个视频 YouTube - Writing custom Babel and ESLint plugins with ASTs ,我是从 babel-plugin-macros 的指南中找到它的。大致清楚了 babel-plugin-macros 做的事情了(帮助用户找到 import 进来所需要的索引,然后用户去根据索引去找到对应的 ast 节点并替换了它)计划与实践基本环境搭建这里花了很多的事情在 yarn lerna jest,以及各种编译工具的调研使用上。

最后决定了使用 rollup-plugin-typescript2 作为 Transformer 的第一个适配对象(因为 rollup 简单啊,而且可配置)以及 microbundle 作为最简单的免配置的编译工具(免配置!没特殊需求时候简直不能更棒!)yarn 的 workspace 很适合我这种需要多个包的构建的项目jest 的 snapshot 功能我很喜欢……在诸多工具都选择好了之后,最后终于搭建了啥代码都没有的空架子。变量使用收集我们需要做 babel-plugin-macros 类似的事情。

我们第一步需要做到收集所有的 import 进来的 declaration 的变量,被哪些地方引用了。

我不知道 Typescript 是否有这种 API,我就厚着脸皮去问了: API about Identifier's scope · Issue #28026 · Microsoft/Typescript · GitHub 然后有个好心的哥们把自己的库推荐给我了,感动到哭。替换节点的 API 设计babel-plugin-macros 采用了直接提供目标节点的列表,让我们直接去替换掉,但是 Typescript 的 transform API 是输入一个节点,输出一个节点,也就意味着,我们不能做到改变父节点。我想来想去,决定给每个自定义的宏传递一个函数 reference。

告诉他们,你传进来的节点是否是当下的宏的引用,大体的设计如 NodeTransformParameter作为宏的作者,只要 export 一个 __typescriptMacroNodeTransformFunction,它的类型签名是 TypescriptMacronodeTransformFunction 就好了遍历的逻辑参考了 babel-plugin-macros, 我会按照每个宏的 import 顺序去遍历一遍全部节点,如果你在一个文件 import 了十次宏,那就会遍历十遍。见 transformerFactoryCreator,遍历的时候是一个递归的处理,见于: visitEachChild样例的编写为了证明我的宏引擎的确有效,我按时间顺序大致写了这么几个宏

  • uppercase.tsmacro 证明的确能在编译期做事情
  • console-scope.tsmacro 证明能做一些好玩的事情,比如帮忙打出一堆 log
  • hooks.tsmacro 证明能写有用的代码,比如生成 React Hooks 的一些烦人的需要手填的参数。这个还有个小 bug,你猜猜是啥?
The array of inputs is not passed as arguments to the effect function. Conceptually, though, that’s what they represent: every value referenced inside the effect function should also appear in the inputs array. In the future, a sufficiently advanced compiler could create this array automatically.
  • transformer-keys.tsmacro 证明可以把编译期的类型参数放到运行时,我们也许可以做更多,比如这个知乎讨论中说的事情
  • interop-export-macros.tsmacro 和 lowercase.macro 证明和 babel-plugin-macros 可以和谐共存
后记其实这还有超级多的坑存在,而且缺乏相应的讨论和反馈,有兴趣的可以来和我吵架。而且我其实已经操着不及格的英语水平在吵架了呢,而且这里的讨论也给我攒了三十几个 star, 见:Let's discuss Typescript support. · Issue #94 · kentcdodds/babel-plugin-macros · GitHub ,你们有兴趣也可以参与进来啊。

 
友情链接
鄂ICP备19019357号-22