代码生成-从 module css 生成 dts
本文最后更新于:2022年10月18日 上午
前言
代码生成对于很多开发者都不是陌生的概念,从使用脚手架(create-react-app)生成项目,到使用 ide 生成代码、或是从后端 api schema 生成代码,几乎不可能避免使用它。它可以解决各种各样的问题
- 从同一个来源生成项目,避免千人千面的项目整体结构
- 减少编写样板代码
- 避免在多个地方重复编写代码导致的不一致性
但在使用 TypeScript 时,它还可以做到一些其他有趣的事情,包括
- 生成类型提高开发者体验,例如为 env、module css、i18n config 生成类型定义
- 支持原本不支持引入的文件,例如为 graphql 生成代码音变引入它
或许有人会认为代码生成需要处理 ast(即抽象语法树),而处理 ast 是一件复杂的事情,因而不去尝试做类似的事情。吾辈要说的是,ast 的实际结构确实可能会很复杂,例如 TypeScript 官方解析器解析 ts 得到的那个,但其核心却相当简单,这个领域仅仅只是门槛稍微有点高。如果你选择了一个合适的语法树操作工具,再加上现有的各种 代码 <=> ast 可视化工具,那事情会变得简单许多。
基本
想要生成代码,基本上就像将一只大象放进冰箱里面一样需要三步
- 得到某种类型的元数据,例如从 css 得到它的 ast
- 转换元数据得到生成目标代码的 ast
- 将 ast 转换成代码
正如标题所言,在这里主要的目标代码是 TypeScript,相应的,元数据的来源多种多样,从 json 数据、到其他语言的 ast、到远端的接口,这实际上没有什么限制,只要你能够将之转换为目标 ast 即可。
下面我们将来尝试第一个,也是最简单的一个,从 module css 文件生成类型定义
从 css 生成类型定义
动机
为什么要这样做?
在使用 css module 时,我们通常使用构建工具,例如 rollup/vite/webpack 来解析 *.module.css 文件,并使得最终 bundle 中的结果符合预期。但在开发阶段,它并没有太多提示,例如定义了一个 css class,但你在 ts 中使用时,并不会有什么提示。当你将一个 css class 删除后,也不会有任何代码提示。
例如下面这个示例
1 |
|
1 |
|
如果我们在 App.module.css 旁边放一个 App.module.css.d.ts 文件,那么在 ts 中使用时就会很快乐
1 |
|
当然,在 ide 中也可以完成这个功能,但每个 ide 都需要实现一遍这个功能,这正是问题所在,插件无法跨 ide 使用,但基于 TypeScript 的代码提示可以做到跨 ide 使用,包括 vscode、jetbrains ide、vim 等等。
技术选型
正如上面所言,想要生成代码,在这里需要得到 css ast 和转换 css ast 为 ts ast,而这就需要选择一个合适的解析器来解析 css 得到 ast 以及生成 ts ast 并转换为代码了。
我们使用以下两个库
- css-tree: 解析 css 代码为 cssom
- ast-types: 一个通用的 ts/js ast 高层次抽象
- recast: 基于 ast-types 的一个 ast 解析生成器
Tip: 代码的 ast 可以在 https://astexplorer.net/ 以可视化的方式检查
解析 css
首先,解析 css 得到 ast,并从中过滤出所有 class 选择器类名
1 |
|
然后将 css 类名列表转换为 ast
1 |
|
最后,将 ast 转换为 ts 代码
1 |
|
结合一下 3 个方法
1 |
|
做个最简单的测试
1 |
|
看起来我们完成了基本的从 css 到 dts 的代码生成,但如果希望实用,它还需要一些额外的步骤
- 更好的使用方式封装,例如封装为 cli 自动扫描指定目录下的所有 *.module.css 文件并生成对应的 dts 文件,或者是通过插件直接集成到开发工具的流程中,例如 vite 插件
- 发布为 npm 包,或者使用某种形式的 monorepo 便于在多个项目复用
下面使用 vite 插件作为演示
1 |
|
现在,每当启动 vite 时都会自动扫描所有的 *.module.css 生成对应的类型定义,在开发模式下还会持续监听文件的变化。
sourcemap
目前已经实现了代码提示和校验的功能,但跳转尚未生效,我们可以使用 source-map 来实现它。这是另一个有趣的技术,在之后将详细介绍,这里仅说明一下工作方式。sourcemap 将一个文件的内容与一或多个源文件的内容映射,chrome 或 vscode 均支持根据 sourcemap 自动查找对应的源文件,利用这个功能我们可以让生成的 dts 指向 css 文件。
效果
完整代码
1 |
|
结语
在之后的几篇文章中,吾辈将演示代码生成的实际用途,并实现一些简单的例子,也会给出现有的更完善的的工具(如果有的话)。
- 代码生成-从环境变量生成类型定义
- 代码生成-从 i18n config 生成类型定义
- 从 graphql 生成代码
- 从 open api schema 生成类型定义