skip to content
usubeni fantasy logo Usubeni Fantasy

拥抱 TypeScript 的历程

/ 7 min read

Mind Elixir 在 2021 年 10 月开始逐渐迁移到 TypeScript,很忏愧地说直到最近,我才狠下心来打开了把 compilerOptions.strict 设成了 true

那时不太懂 TS,跌跌撞撞地写类型,到现在算是积累了点经验,而且日常工作中的项目也已经用上了 TS。所以大概是时候了,正式把整个项目变成 TS 项目,并且支持 TS 文件引入时获得类型提示。

讲讲这迁移路上的一些事吧,不得不说 vanilla JavaScript 项目迁移到 TS 是真的简单,基本上加个 loader(如果你的 bundler 适配的话甚至不用加),加 tsconfig.json,重点基本就下面两个选项:

{
"compilerOptions": {
"strict": false, // <- 迁移时设为 false 起步十分方便
"allowJs": true // <- 允许 js 共存
}
}

配置完成后,把 .js 文件改为 .ts 文件,接着开始着手添加类型就完事了。

在慢慢加类型的时候第一个问题来了,我写的全局自定义类型要放哪里呢?如果现在向我提问的话果然直接还是走 ESM 算了,放在普通 ts 文件,export 它,然后 import type。虽然当时也知道有这个办法,但是觉得全部类型全都 export import 实在太烦了,明明就是一个库,为什么他不能直接写在一个文件夹里由 IDE 自动感应就好了呢?

大概基于这个想法,我老是在想 d.ts,其实 2021 年的主流也不至于还是 d.ts,但我还是选了,现在看来感觉这步我是走错了。

不仅错了,而且这步其实也不太好走,我想方设法放置我的全局变量,发现有时候总是检测不了,至于当时怎么写的我也忘了,就不提了,反正也没什么值得注意的地方。后来,我遇到了这篇文章 A quick introduction to “Type Declaration” files and adding type support to your JavaScript packages,然后我惊呼:真不错!

我一下就抓住了救命稻草 /// <reference path="./xxx.d.ts" />

这个方案需要在 tsconfig.json 设置 typeRoots。默认情况下,TS 会在 node_modules/@types 目录中查找类型定义文件,但是显然不是每一个库都提供了类型渲染,于是你可以添加 typeRoots,然后 TS 就会在 typeRoots 里找。

tsconfig.json
{
"compilerOptions": {
"typeRoots": ["./types"]
}
}

在刚才设置的 types 文件夹里多加个 common 文件夹区分不同包,还可以通过 package.jsontypings 配置入口文件。

types/common/package.json
{
"name": "common",
"version": "1.0.0",
"typings": "main.d.ts"
}

之后你无论把类型分多少个文件,只要在入口文件添加 /// <reference path="./xxx.d.ts" /> 就能引入这个文件,不需要 import,一切都是那么的自然。这个方案挺好的,很方便,随时加 type,全局可用,但是……

Mind Elixir 本身就是要给别人用的,打包之后,使用者要得到类型提示可要咋办?项目本身 TS 开发,但是引入的时候把 TS 丢了,确实挺怪的。

而这个问题的答案,我现在还在思考。

按照 typeRoots 的方案,用户确实可以以同样方法引入我提供的类型,但是我推测这种方法仅仅能拿到我定义的类型,但是实际 import 的时候并不会自动推理出 import 的类型,这也十分致命。

之后我将目光投向 compilerOptions 里输出相关的选项:

{
"compilerOptions": {
"declaration": true,
"emitDeclarationOnly": true,
"outDir": "dist/types"
}
}

这么配置之后,tsc 时会自动帮你按源文件结构输出一份 d.ts,然后在 package.json 里说明你的 type 放哪就行了。这样,用户在 import 时会自动识别出引入内容的类型。

package.json
{
"typings": "dist/types/index.d.ts",
"exports": {
".": {
"types": "./dist/types/index.d.ts",
"import": {
"default": "./dist/MindElixir.umd.cjs",
"import": "./dist/MindElixir.js"
},
"require": "./dist/MindElixir.umd.cjs"
}
}
}

然而事情没有这么容易被解决,之前也提到 d.ts 按源文件结构被输出,换言之,如果类型没有被 export 的话,生成的 d.ts 里根本就不存在那些类型,结果就是一大堆类型缺失了,IDE 只会把他们当 any

所以目前我的路就只有两条了,要不老实用回 import type 那条我从一开始就没选择的路,要不再想想有什么办法能自动生成没有引入的类型,不过这条路是希望渺茫啦,所以……

还是免不了啪啪打自己的脸,慢慢改回 import type 吧,然后再下一步就是吧 allowJs 设为 false,迁移就完工了,提前撒花 ★,°:.☆( ̄ ▽  ̄)/$:.°★

P.S. 最后还是得说一句,TS 最最最最最最最最最不友好的地方自然,是对构造函数的支持,真的烦死,逼人用 class,但是真的不想换,最后坚持普通构造函数加一些歪门邪道解决问题

评论组件加载中……