Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

如何实现一个 TypeScript 的宏 #186

Open
xiaoxiangmoe opened this issue Dec 15, 2018 · 0 comments
Open

如何实现一个 TypeScript 的宏 #186

xiaoxiangmoe opened this issue Dec 15, 2018 · 0 comments

Comments

@xiaoxiangmoe
Copy link

如何实现一个 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 (Open West 2017),我是从 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(ret, visitor)

样例的编写

为了证明我的宏引擎的确有效,我按时间顺序大致写了这么几个宏

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.

后记

其实这还有超级多的存在,而且缺乏相应的讨论和反馈,有兴趣的可以来和我吵架。而且我其实已经操着不及格的英语水平在吵架了呢,而且这里的讨论也给我攒了三十几个 star, 见:Let's discuss TypeScript support. · Issue #94 · kentcdodds/babel-plugin-macros · GitHub ,你们有兴趣也可以参与进来啊。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants