5

拥抱 TypeScript 的历程

 1 year ago
source link: https://ssshooter.com/typescript-tittle-tattle/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

拥抱 TypeScript 的历程

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,但是真的不想换,最后坚持普通构造函数加一些歪门邪道解决问题


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK