tsc编译typescript项目-TypeScript编译方案及IDE对TS的类型检测

2024-02-13 0 6,843 百度已收录

TypeScript代码的编译过程仍然让很多朋友感到困惑。 Typescript官方提供了tsc来编译ts代码,babel也表示可以编译ts代码。 两者有什么区别? 我们应该选择哪个选项? 为什么IDE打开ts项目时会有这样的ts代码类型定义? 为什么IDE将代码标记为红色并报错,但代码可以编译?

带着这个问题,我们将由浅入深地介绍一下TypeScript代码编译的两种解决方案以及我们日常使用IDE检测ts文件类型之间的关系,以便大家以后面对基于ts的项目时更加得心应手。

写在上面

事实上,这篇文章并不是一篇全新的文章。 早在8月22日,我就写过一篇文章,名为《TypeScript、Babel、webpack与IDE对TS的类型检测的关系》。 内容包括但由于当时编写仓促,整篇文章的结构安排得不好,脉络也不清楚。 我把想到的都说了,同时也想介绍一下webpack相关的东西,所以最后的内容越来越乱。 作为一个有强迫症的人,我对当时的文章还是不太满意。

最近正好在写一篇关于TSX(基于TypeScript代码的JSX代码)的类型检测的介绍,所以当时重新编辑了文章,重新整理了内容,增加了更多的图表,并删除了相关的webpack部分重点介绍了目前TypeScript代码的编译方案,使得文章的内容更加有针对性。 在三部曲的第二部分中,我们将重点介绍本文中删除的如何为 webpack 项目编译 TypeScript 项目的内容(考虑到这部分内容需要本文的基础,所以放在第二部分 )。 最后一部分将介绍TSX型检测。

TypeScript 基本原理

原则一:主流浏览器的主流版本只看懂js代码

原则二:ts代码必须编译成js代码才能在主流浏览器上运行

TypeScript编译方法

首先,如果要编译ts代码,至少必须具备以下三个要素:

ts源码 ts编译器 ts编译配置

上述过程为:ts编译器读取ts源码,通过指定的编译配置,将ts源码按照指定的方式编译成js代码。

目前主流的ts编译方案有两种,分别是:

tsc 编译 babel 编译

接下来我们将详细介绍上述两种方案以及它们之间的区别。

tsc 编译

官方的编译方案,根据TypeScript官方手册,需要使用tsc(TypeScript Compiler)来完成。 tsc 来自本地或项目中安装的 typescript 包。

根据前面的ts代码编译这三个元素,我们可以完成一一对应:

ts源码 ts编译器:tscts编译配置:tsconfig.json

让我们通过一个 simple-tsc-demo 来练习这个过程。

首先,创建一个名为 simple-tsc-demo 的空文件夹并执行yarn init(npm init 也可用)。 然后,基于上述三因素模型,我们准备:

(1)ts源码:写入项目根目录/src/index.ts

interface User {
    id: string;
    name: string;
}
export const userToString = (u: User) => `${u.id}/${u.name}`

tsc编译typescript项目-TypeScript编译方案及IDE对TS的类型检测

(2)编译器tsc:通过安装typescript获得

yarn add typescript

(3)编译配置tsconfig.json:项目根目录/tsconfig.json

{
  "compilerOptions": {
    "module": "commonjs",
    "rootDir": "./src",
    "outDir": "./dist"
  }
}

简单介绍一下上面的tsconfig.json配置:

module:指定编译ts代码生成哪个模块解决方案的js代码。 Commonjs暂时写到这里,其他值的差异稍后介绍; rootDir:指定ts代码存放的根目录tsc编译typescript项目,这里是当前目录(项目根目录)的src文件夹可以匹配我们编译的项目根目录/src/index.ts; outDir:指定ts代码编译后生成的js代码的存放目录。

当然,为了方便执行命令,我们在package.json中添加一个名为build的脚本:

{
  ... 
+ "scripts": {
+  "build": "tsc"
+ },
  ...
}

建设完成后,总体工程如下:

运行构建脚本,可以看到项目根目录下生成了dist/index.js:

关于index.js的内容,熟悉js模块化规范的朋友应该很容易看出,这是commonjs的规范:在exports对象中添加一个属性数组,exports对象就会作为模块导入并被其他模块使用模块。

之所以形成的js代码是符合commonjs模块规范的代码,是因为我们在tsconfig.json中配置的模块值为commonjs。 如果我们将模块数组更改为 es6:

{
  "compilerOptions": {
- 	"module": "commonjs",
+   "module": "es6",
    "rootDir": "./src",
    "outDir": "./dist"
  }
}

再次编译后,你会听到编译后的js代码是符合es6模块规范的代码:

对于ts​​c编译方案,基于TypeScript的三元编译模型简单总结一下:我们设计了ts源码、tsc编译器和tsconfig.json配置。 通过tsc编译器读取tsconfig.json编译配置,将ts源码编译为js代码。 另外,在tsconfig.json中,我们配置了生成的js代码的两个模块规范:“module”:“commonjs”和“module”:“es6”,并验证结果符合相应的模块规范。

至于编译器部分,除了我们之前尝试过的tsc编译器之外,还有其他的编译器吗? 答案是肯定的:通天塔。

巴别塔编译

本文并不是专门介绍 Babel 的文章,但是为了更好的衔接相关知识,这个内容还是需要介绍一下。 当然,如果读者有时间,我推荐这篇深入了解 Babel 的文章:Understanding Babel in one (very long) Breath - 知乎(zhihu.com)。

Babel分为三个阶段:解析、转换、生成。

Babel本身没有任何转换功能。 它将转换函数分解为单独的插件。 所以当我们不配置任何插件时,通过 babel 的代码和输入是相同的。

tsc编译typescript项目-TypeScript编译方案及IDE对TS的类型检测

有两种类型的插件:

举个简单的例子,当我们定义OR调用方法时,最后一个参数后面的逗号是不允许的。 例如,callFoo(param1, param2,) 是非法的。 如果源码这样写,babel后会提示句型错误。 但最近的 JS 提案已经允许这些新的编写方式(使代码差异更清晰)。 为了防止babel报错,需要减少句型插件babel-plugin-syntax-trailing-function-commas

与句子插件相比,翻译插件更容易理解。 例如,箭头函数 (a) => a 将被转换为函数 (a) {return a}。 执行此操作的插件称为 babel-plugin-transform-es2015-arrow-functions。

同一类型的句型可以同时具有句型插件版本和翻译插件版本。 如果我们使用翻译插件,就不再需要使用句型插件了。

综上,Babel转换代码如下:

源代码 -(babel)-> 目标代码

如果不使用插件,源代码和目标代码没有区别。 当我们引入各种插件时,流程如下:

源代码
|
进入babel
|
babel插件1处理代码,例如移除某些符号
|
babel插件2处理代码,例如将形如() => {}的箭头函数,转换成function xxx() {}
|
目标代码

Babel 提倡专注于一件事的插件。 例如,插件仅进行箭头函数转换,插件仅将 const 转换为 var 代码。 这样设计的目的是为了灵活组合各种插件来完成代码转换。

但由于 Babel 的插件处理非常细致,所以 JS 代码的句型规范也很多。 为了处理这个句型,可能需要配置很多插件。 为了解决这个问题,Babel 设计了预设(preset set)的概念,它结合了一堆插件。 因此,我们只需要引入一个插件包预设即可处理代码的各种句型。

PS:正式合并的插件包一般以“@babel/plugin-”开头,而预设包一般以“@babel/preset-”开头。

回到 TypeScript 编译,对于 Babel TS 编译系统,我们也按照 TypeScript 编译的三元模型一一对应:

ts源码 ts编译器:babel+相关预设,plugins编译配置:.babelrc

同样,让我们​​通过一个 simple-babel-demo 来练习这个过程。

首先,创建一个名为 simple-babel-demo 的空文件夹并执行yarn init(npm init 也可用)。 然后,基于上述三因素模型,我们准备:

(1)源码:编写项目根目录/src/index.ts

interface User {
    id: string;
    name: string;
}
export const userToString = (u: User) => `${u.id}/${u.name}`

(2)ts编译器babel+相关预设和插件:项目安装以下依赖包

yarn add -D @babel/cli @babel/core
yarn add -D @babel/preset-env @babel/preset-typescript
yarn add -D @babel/plugin-proposal-object-rest-spread

读者看到这么多依赖包需要安装时不要感到着急。 我们来一一分析:

看完介绍,你是不是有了一些清晰的认识了呢? 让我们继续讨论三个元素中的最后一个:编译配置。

(3)编译配置.babelrc:项目根目录/.babelrc文件

tsc编译typescript项目-TypeScript编译方案及IDE对TS的类型检测

{
  "presets": [
    "@babel/preset-env",
    "@babel/preset-typescript"
  ],
  "plugins": [
    "@babel/plugin-proposal-object-rest-spread"
  ]
}

上面的配置并不复杂,对应我们安装依赖包的preset和plugin部分。 这部分配置还告诉 Babel 需要加载哪些预设和插件,以便它们可以处理代码。

最后,我们在package.json中添加编译脚本:

{
	...
+ "scripts": {
+ 	"build": "babel src --config-file ./.babelrc -x .ts -d dist"
+ },
	...
}

编译指令指定babel要读取的源代码所在目录(src)、babel配置文件地址(--config-file ./.babelrc)、babel需要处理的文件扩展名(-x . ts),以及编译后的代码生成目录。 (-d 距离)。

项目建设完成后,总体结构如下:

运行构建脚本,可以看到项目根目录下生成了dist/index.js:

这段代码和之前tsc基于commonjs编译的js代码没有太大区别。 也就是说babel可以基于@babel/preset-env+@babel/preset-typescript将TS代码编译成commonjs代码。 那么我们如何使用babel将ts代码编译成es6代码呢? 从babel配置开始,其实我们只需要去掉babelrc的@babel/preset-env即可:

{
  "presets": [
-  	"@babel/preset-env",
    "@babel/preset-typescript"
  ],
  "plugins": [
    "@babel/plugin-proposal-object-rest-spread"
  ]
}

再次编译后可以看到生成的index.js符合es6规范:

对于babel编译,我们简单总结一下。 对应TypeScript编译的三元模型,我们计划使用ts源码、babel及相关预设和插件作为编译器,babelrc作为编译配置。 babel的代码处理流程启动后,根据编译配置知道需要加载哪些插件和预设,并将代码和相关信息交给插件和预设进行处理,最后编译成js代码。 另外,在babelrc中,我们通过配置@babel/preset-env来控制生成符合commonjs或es6模块规范的js代码。

编译总结

不难看出,无论句型系统有多么庞大,类型检测ts有多么强大,最终的产品都是js。

另外需要注意的是,ts 中的模块化不能与 js 中的模块化混淆。 js中有很多模块化的解决方案(es6、commonjs、umd等),所以在ts本身的编译过程中,需要指定js的模块化表达,然后才能编译成相应的代码。 ts中的导入/导出不能被认为与es6中的导入/导出相同。 这是两个完全不同的系统! 只是句子结构相似而已。

tsc编译和babel编译的区别

前面我们介绍了TS代码的tsc编译和babel编译。 它们之间有什么区别? 我们先看这样一个场景:下面是一段ts源码:

interface User {
    id: string;
    name: string;
}
export const userToString = (u: User) => `${u.id}/${u.name}`

我们故意将 u.name 误写为 u.myName:

- export const userToString = (u: User) => `${u.id}/${u.name}`
+ export const userToString = (u: User) => `${u.id}/${u.myName}`

预计来说,类型检测肯定会失败,因为 User 套接字根本没有名称数组。 我们分别看看tsc编译和babel编译的编译结果是否符合我们的预期。

tsc编译typescript项目-TypeScript编译方案及IDE对TS的类型检测

tsc编译错误代码

从结果中可以清楚地看到,在使用tsc编译错误代码时,tsc类型检测帮助我们找到了代码中的错误点,符合我们的预期。

babel编译错误代码

从结果来看,babel编译可以直接成功! 查看生成的index.js代码:

export const userToString = u => `${u.id}/${u.myName}`;

从js代码来看,这段代码没有问题。 此时u参数变量在js层面并没有明确的类型定义。 js是一种动态语言。 运行时,myName也可能存在。 谁能 还不确定。

为什么babel编译处理代码是这样的? 这就不得不提到babel中的@babel/preset-typescript是如何编译TS代码的:

警告! 有一些令人惊讶的消息,您可能想坐下来听。

Babel 如何处理 TypeScript 代码? 它删除它。

是的,它剥离了所有 TypeScript,将其转换为“常规”JavaScript,并继续以自己的方式旋转。

这听起来很愚蠢,但这些技术有两大优点。

第一个优点:快如闪电。

大多数 Typescript 开发人员在开发/监控模式下都会遇到编译时间过长的问题。 您正在编写代码,保存文件,然后...它出现...然后...最后,您会看到您的更改。 哎呀,有一个词错了,改正,保存tsc编译typescript项目,然后……啊。 它的速度慢得令人痛苦,并且会扼杀你的动力。

很难与 TypeScript 编译器争论,它做了很多出色的工作。 它扫描这些类型定义文件(*.d.ts),包括node_modules,并确保您的代码被正确使用。 这就是为什么许多人将 Typescript 类型检查分离到一个单独的进程中的原因。 然而,得益于 Babel 的中级缓存和单文件发射架构,Babel + TypeScript 组合始终能够提供更快的编译速度。

换句话说,当 babel 处理 TypeScript 代码时,它不会执行任何类型检测! 那么小伙伴们可能会说,如果我使用babel编译方案,如何进行类型检测来保证ts代码的正确性呢? 答案是:引入tsc,但只进行类型检测。

回到之前的 simple-babel-example。 在上一篇的基础上,我们仍然安装typescript并获取tsc:

{
	...
	"devDependencies": {
    "@babel/cli": "^7.21.0",
    "@babel/core": "^7.21.4",
    "@babel/plugin-proposal-object-rest-spread": "^7.20.7",
    "@babel/preset-env": "^7.21.4",
    "@babel/preset-typescript": "^7.21.4",
+   "typescript": "^5.0.4"
  }
}

然后,将tsconfig.json文件添加到项目中,配置如下

{
  "compilerOptions": {
    "noEmit": true,
    "rootDir": "src"
  }
}

与 tsc 编译方案上面的配置相比,babel 编译方案中用于类型检测的 tsconfig.json 需要我们将 noEmit 配置为 true,也就是说 tsc 读取 ts 源码后,不会生成任何文件,只执行类型检测。

因此,在Babel编译方案中,整个系统如下:

tsc编译typescript项目-TypeScript编译方案及IDE对TS的类型检测

主流IDE如何对TS项目进行类型检测?

不知道有细心的读者会发现,在使用IDEA时,如果是IDE当前打开的TS文件,IDEA的右下角会显示一个打字稿:

VSCode 还将具有:

在同一台笔记本电脑上,我什至发现IDEA和VSCode的typescript版本不同(5.0.3和4.9.5)。 到底是怎么回事? 事实上,当IDE检测到你所在的项目是ts项目(或者当前正在编辑ts文件)时,它会手动启动一个ts检查服务,该服务专门用于对当前ts代码进行类型检查。 这个ts类型检查服务也是使用tsc来完成的,但是这个tsc来自于两种方式:

默认情况下,每个 IDE 都附带 typescript tsc。 当前项目已安装 typescript tsc。

比如上图中我机器上的IDEA测量到项目中安装了“typescript”:“^5.0.3”,所以我手动切换到为项目安装的TypeScript; 而VSCode没有测,所以我用的是VSCode自带的。

当然,你也可以在IDE中自动切换:

最后,我们简单回顾一下 IDE 如何在相应的代码位置显示代码类型错误。 流程如下:

不过IDE中的ts类型检测也必须有一定的基础。 例如,在哪里可以找到外部库的类型定义文件、是否允许使用较新的句型等。这些配置仍然由 tsconfig.json 提供,但如果不提供,IDE 将使用默认配置。 如果要执行类型检查的自定义配置,则需要提供 tsconfig.json。

编译方案与IDE类型检测一体化

结合上面的 tsc 编译和 babel 编译流程,再梳理一下上面 IDE 对 TS 项目的类型检测,我们可以分别总结一下 tsc 编译和 babel 编译两种场景的代码编译流程和 IDE 类型检测流程。

首先是tsc编译方案:

在这个解决方案中,ts项目代码本身的编译将使用项目安装的typescript并加载项目本身的tsconfig.json配置。 同时IDE也会利用项目本身的typescript和读取相同配置的tsconfig.json来完成项目代码的类型检测。

因此,IDE 提供的代码编译和类型检测都遵循一组逻辑。 当IDE提示个别ts代码编译问题时,编译ts代码时肯定会出现同样的问题。 永远不会出现代码有编译问题,但IDE不以绿色显示类型检测问题的情况。

我们看一下babel编译的解决方案:

显然,babel编译方案,代码编译和IDE类型检测是两条路线。 也就是说,有可能你的IDE提示错误,但是babel编译没有问题。 这也是为什么很多朋友收到基于babel编译的TS项目在IDE中很容易出现代码异常的UI显示问题,但编译出来的代码却没有问题的原因。

写在最后

本文重点介绍两种 TypeScript 代码的编译方案,以及 IDE 如何进行 TypeScript 类型检测。 作为三部曲的第一部,内容非常多,也比较详细。 感谢您的耐心阅读。 剩下的两部分将介绍 webpack 如何基于 TypeScript 编译和打包项目以及 TSX 如何进行类型检测。

收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

悟空资源网 typescript tsc编译typescript项目-TypeScript编译方案及IDE对TS的类型检测 https://www.wkzy.net/game/200203.html

常见问题

相关文章

官方客服团队

为您解决烦忧 - 24小时在线 专业服务