只要接触过ts的前端同学都能回答出ts是js超集,它具备静态类型分析,能够根据类型在静态代码的解析过程中对ts代码进行类型检查,从而在保证类型的一致性。那,现在让你对你的webpack项目(其实任意类型的项目都同理)加入ts,你知道怎么做吗?带着这个问题,我们由浅入深,逐步介绍TypeScriptBabel以及我们日常使用IDE进行ts文件类型检查的关系,让你今后面对基于ts的工程能够做到游刃有余。

TypeScript基本认识

原则1:主流的浏览器的主流版本只认识js代码

原则2:ts的代码一定会经过编译为js代码,才能运行在主流浏览器上

要编译ts代码,至少具备以下几个要素:

  1. ts源代码
  2. ts编译器
  3. ts编译器所需要的配置(默认配置也是配置)

编译TS的方式

目前主流的ts编译方案有2种,分别是官方tsc编译、babel+ts插件编译。

官方tsc编译器

对于ts官方模式来说,ts编译器就是tsc(安装typescript就可以获得),而编译器所需的配置就是tsconfig.json配置文件形式或其他形式。ts源代码经过tsc的编译(Compile),就可以生成js代码,在tsc编译的过程中,需要编译配置来确定一些编译过程中要处理的内容。

我们首先准备一个ts-demo,该demo中有如下的结构:

ts-demo
|- packages.json
|- tsconfig.json
|- src
|- index.ts

安装typescript

yarn add -D typescript

package.json

{
"name": "ts-demo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"build-ts": "tsc"
},
"author": "",
"license": "MIT",
"devDependencies": {
"typescript": "^4.7.4"
}
}

tsconfig.js(对于这个简单的tsconfig,我不再赘述其配置的含义。)

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

index.ts

interface User {
name: string;
age: number;
}
const userToString = (user: User) => `${user.name}@${user.age}`;
export {userToString, User};

此时,我们只需要运行yarn build-ts就可以将我们的index.ts编译为index.js:

commonjs模块化方式产物:

"use strict";
exports.__esModule = true;
exports.userToString = void 0;
var userToString = function (user) { return "".concat(user.name, "@").concat(user.age); };
exports.userToString = userToString;

可以看到,原本index.ts编译为index.js的产物,使用了commonjs模块化方案(tsconfig里面配置模块化方案是"commonjs",编译后的代码可以看到"exports"的身影);倘若我们将模块化方案改为ESM(ES模块化)的es:"module": "es6",编译后的产物依然是index.js,只不过内容采用了es6中的模块方案。

es6模块化方式产物:

var userToString = function (user) {
return "".concat(user.name, "@").concat(user.age);
};
export {userToString};

说了这么多,只是想要告诉各位同学,ts无论有多么庞大的语法体系,多么强大的类型检查,最终的产物都是js

此外,ts中的模块化,不能和js中的模块化混为一谈。js中的模块化方案很多(es6、commonjs、umd等等),所以ts本身在编译过程中,需要指定一种js的模块化表达,才能编译为对应的代码。也就是说,在ts中的import/export,不能认为和es6的import/export是一样的,他们是完全不同的两个体系!只是语法上类似而已。

babel+ts插件

如前文所述

ts源代码经过tsc的编译(Compile),就可以生成js代码,在tsc编译的过程中,需要编译配置来确定一些编译过程中要处理的内容。

那么是不是说,编译器这块是不是有其他的代替呢?ts源码经过某种其他的编译器编译后,生成目标js代码。答案是肯定的:babel。

我们准备一个ts-babel-demo:

ts-babel-demo
|- packages.json
|- .babelrc
|- src
|- index.ts

依赖添加:

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

package.json:

{
"name": "ts-babel-demo",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"private": true,
"scripts": {
"build": "babel src -d dist -x '.ts, .tsx'"
},
"devDependencies": {
"@babel/cli": "^7.18.10",
"@babel/core": "^7.18.10",
"@babel/plugin-proposal-class-properties": "^7.18.6",
"@babel/plugin-proposal-object-rest-spread": "^7.18.9",
"@babel/preset-env": "^7.18.10",
"@babel/preset-typescript": "^7.18.6"
}
}

.babelrc

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

index.ts和ts-demo保持一致。

完成基础的项目搭建以后,我们执行yarn build

~/Projects/web-projects/ts-babel-demo > yarn build
yarn run v1.22.17
$ babel src -d dist -x '.ts, .tsx'
Successfully compiled 1 file with Babel (599ms).
Done in 4.05s.

可以看到项目dist目录下出现了编译好的js代码:

"use strict";

Object.defineProperty(exports, "__esModule", {
value: true
});
exports.userToString = void 0; var userToString = function userToString(user) {
return "".concat(user.name, "@").concat(user.age);
}; exports.userToString = userToString;

可以看到和使用tsc编译为commonjs效果是一样。

回顾这个项目,其实按照我们之前的思路来梳理:

  1. ts源文件(src/index.ts)
  2. ts的编译器(babel)
  3. 编译配置(.babelrc)

了解babel机制

如果对于babel不太熟悉,可能对上述的一堆依赖感到恐惧:

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

这里如果读者有时间,我推荐这篇深入了解babel的文章:一口(很长的)气了解 babel - 知乎 (zhihu.com)。当然,如果这口气憋不住(哈哈),我做一个简单摘抄:

babel 总共分为三个阶段:解析,转换,生成。

babel 本身不具有任何转化功能,它把转化的功能都分解到一个个 plugin 里面。因此当我们不配置任何插件时,经过 babel 的代码和输入是相同的。

插件总共分为两种:

  • 当我们添加 语法插件 之后,在解析这一步就使得 babel 能够解析更多的语法。(顺带一提,babel 内部使用的解析类库叫做 babylon,并非 babel 自行开发)

举个简单的例子,当我们定义或者调用方法时,最后一个参数之后是不允许增加逗号的,如 callFoo(param1, param2,) 就是非法的。如果源码是这种写法,经过 babel 之后就会提示语法错误。

但最近的 JS 提案中已经允许了这种新的写法(让代码 diff 更加清晰)。为了避免 babel 报错,就需要增加语法插件 babel-plugin-syntax-trailing-function-commas

  • 当我们添加 转译插件 之后,在转换这一步把源码转换并输出。这也是我们使用 babel 最本质的需求。

比起语法插件,转译插件其实更好理解,比如箭头函数 (a) => a 就会转化为 function (a) {return a}。完成这个工作的插件叫做 babel-plugin-transform-es2015-arrow-functions

同一类语法可能同时存在语法插件版本和转译插件版本。如果我们使用了转译插件,就不用再使用语法插件了。

简单来讲,使用babel就像如下流程:

源代码 =babel=> 目标代码

如果没有使用任何插件,源代码和目标代码就没有任何差异。当我们引入各种插件的时候,就像如下流程一样:

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

因为babel的插件处理的力度很细,我们代码的语法、语义内容规范有很多,如果我们要处理这些语法,可能需要配置一大堆的插件,所以babel提出,将一堆插件组合成一个preset(预置插件包),这样,我们只需要引入一个插件组合包,就能处理代码的各种语法、语义。

所以,回到我们上述的那些@babel开头的npm包,再回首可能不会那么迷茫:

@babel/core
@babel/preset-env
@babel/preset-typescript
@babel/preset-react
@babel/plugin-proposal-class-properties
@babel/plugin-proposal-object-rest-spread
  • @babel/core毋庸置疑,babel的核心模块,实现了上述的流程运转以及代码语法、语义分析的功能;

  • @babel/cli则是我们可以在命令行使用babel命令;

  • plugin开头的就是插件,这里我们引入了两个:@babel/plugin-proposal-class-properties允许类具有属性)和@babel/plugin-proposal-object-rest-spread对象展开);

  • preset开头的就是预置组件包合集,其中@babel/preset-env表示使用了可以根据实际的浏览器运行环境,会选择相关的转义插件包,通过配置得知目标环境的特点只做必要的转换。如果不写任何配置项,env 等价于 latest,也等价于 es2015 + es2016 + es2017 三个相加(不包含 stage-x 中的插件);@babel/preset-typescript会处理所有ts的代码的语法和语义规则,并转换为js代码。

关于babel编译ts,并不是所有的语法都支持,这里有一篇文章专门介绍了其中注意点:《TypeScript 和 Babel:美丽的结合》。

webpack项目级TS使用

前面的内容,我们已经介绍了将ts编译为js的两种方式(tsc、babel),但仅仅是简单将一个index.ts编译为index.js。实际上,对于项目级别的ts项目,还有很多需要了解的。接下来基于一个webpack项目来逐步介绍如何基于前文的两种方式来使用ts。

对于webpack来说,至少需要读者了解到webpack的基本机制:概念 | webpack 中文文档 (docschina.org)

简单来讲,webpack运行从指定的entry文件开始,从顶层开始分析依赖的内容,依赖的内容可以是任何的内容(只要是import的或require了的),而loader可以专门来处理各种类型的文件。

webpack 只能理解 JavaScript 和 JSON 文件,这是 webpack 开箱可用的自带能力。loader 让 webpack 能够去处理其他类型的文件,并将它们转换为有效 模块,以供应用程序使用,以及被添加到依赖图中

所以,当一个webpack项目是基于TS进行的时候,我们一定会有一个loader来处理ts(甚至是tsx)。当然,我们还是通过demo搭建来演示讲解。

ts-loader

mkdir webpack-ts-loader-demo && cd webpack-ts-loader-demo
yarn init
yarn add -D webpack webpack-cli
yarn add -D ts-loader

package.json

{
"name": "webpack-ts-loader-demo",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"scripts": {
"build": "webpack --config webpack.config.js"
},
"devDependencies": {
"ts-loader": "^9.3.1",
"webpack": "^5.74.0",
"webpack-cli": "^4.10.0"
}
}

webpack.config.js

const {resolve} = require('path');
module.exports = {
entry: './src/index.ts',
output: {
path: resolve(__dirname, './dist'),
filename: "index.js"
},
module: {
rules: [
{
test: /\.ts/,
loader: "ts-loader"
}
]
}
};

src/index.ts

interface User {
name: string;
age: number;
}
const userToString = (user: User) => `${user.name}@${user.age}`;
export {userToString, User};

表面上,只需要上述三个文件,就可以编译ts文件,但是尝试运行yarn build会报错:

Module build failed (from ./node_modules/ts-loader/index.js):
Error: Could not load TypeScript. Try installing with `yarn add typescript` or `npm install typescript`. If TypeScript is installed globally, try using `yarn link typescript` or `npm link typescript`.

通过报错很容易理解,我们没有安装typescript。为什么?因为ts-loader本身处理ts文件的时候,本质上还是调用的tsc,而tsc是typescript模块提供的。因此,我们只需要yarn add -D typescript即可(其实只需要开发依赖即可),但是紧接着又会有另外一个报错:

ERROR in ./src/index.t
Module build failed (from ./node_modules/ts-loader/index.js):
Error: error while parsing tsconfig.json

报错提醒我们,解析tsconfig的出错,不难理解,我们还没有配置tsconfig.json,因为tsc需要!所以,在我们项目中,加上tsconfig.json即可:

tsconfig.json

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

配置完成以后,我们再次编译,发现可以编译成功,并且在dist目录下会有对应的js代码。

然而,事情到这里就结束了吗?一个中大型的项目,必然有模块的引入,假如现在我们添加了个utils.ts

export const hello = () => {
return 'hello';
}

修改index.ts的代码,引入该hello方法,并使用:

import {hello} from "./utils"; // 引入utils
interface User {
name: string;
age: number;
}
const userToString = (user: User) => `${user.name}@${user.age}${hello()}`;
export {userToString, User};

再次运行yarn build,读者会发现还是会报错,但这一次的错误略有点出乎意料:

Module not found: Error: Can't resolve './utils' in '/Users/w4ngzhen/Projects/web-projects/webpack-ts-loader-demo/src'
resolve './utils' in '/Users/w4ngzhen/Projects/web-projects/webpack-ts-loader-demo/src'

核心报错在于,webpack似乎无法找到utils这个模块。为什么呢?因为webpack默认是处理js代码的,如果你的代码中编写了import xxx from 'xxx',在没有明确指明这个模块的后缀的时候,webpack只会认为这个模块是以下几种:

  1. 无后缀文件
  2. js文件
  3. json文件
  4. wasm文件

所以,你会看到具体一点的报错:

resolve './utils' in '/Users/w4ngzhen/Projects/web-projects/webpack-ts-loader-demo/src'
using description file: /Users/w4ngzhen/Projects/web-projects/webpack-ts-loader-demo/package.json (relative path: ./src)
Field 'browser' doesn't contain a valid alias configuration
using description file: /Users/w4ngzhen/Projects/web-projects/webpack-ts-loader-demo/package.json (relative path: ./src/utils)
no extension
Field 'browser' doesn't contain a valid alias configuration
/Users/w4ngzhen/Projects/web-projects/webpack-ts-loader-demo/src/utils doesn't exist
.js
Field 'browser' doesn't contain a valid alias configuration
/Users/w4ngzhen/Projects/web-projects/webpack-ts-loader-demo/src/utils.js doesn't exist
.json
Field 'browser' doesn't contain a valid alias configuration
/Users/w4ngzhen/Projects/web-projects/webpack-ts-loader-demo/src/utils.json doesn't exist
.wasm
Field 'browser' doesn't contain a valid alias configuration
/Users/w4ngzhen/Projects/web-projects/webpack-ts-loader-demo/src/utils.wasm doesn't exist
as directory

要想让webpack知道我们引入的utils是ts代码,方式为在webpack配置中,指明webpack默认处理的文件后缀:

const {resolve} = require('path');
module.exports = {
// ... ...
resolve: {
// webpack 默认只处理js、jsx等js代码
// 为了防止在import其他ts代码的时候,出现
// " Can't resolve 'xxx' "的错误,需要特别配置
extensions: ['.js', '.jsx', '.ts', '.tsx']
},
// ... ...
};

完成配置以后,我们就能够正确编译具备模块导入的ts代码了。

综合来看,在基于ts-loader的webpack项目的解析流程处理如下。

回顾一下webpack,它默认处理模块化js代码,比如index.js引用了utils.js(模块引用方式可以是commonjs,也可以是esModule形式),那么webpack从入口的index.js出发,来处理依赖,并打包为一个js(暂不考虑js拆分)。

对于wepack+ts-loader的ts项目体系主要是通过ts-loader内部调用typescript提供的tsc,将ts代码编译为js代码(编译后的js代码依然是js模块化的形式),所以这个过程是需要tsconfig参与;等到tsc将整个所有的ts代码均编译为js代码以后,再整体交给webpack进行依赖分析并打包(也就进入webpack的默认处理流程)。

细心的读者会发现这个过程有一个问题:由于先经过tsc编译后的js,又再被webpack默认的js处理机制进行分析并编译打包,这个过程一方面经过了两次编译(ts->标准模块化js->webpack模块体系js),那么如果ts项目特别大,模块特别多的时候,这个两次编译的过程会特别漫长!

babel-loader

前面我们简单介绍了如何使用babel对一份ts进行编译,那么在webpack中,如何使用babel呢?有的同学可能会想到这样操作步骤:我先用babel对ts进行编译为js,然后再利用webpack对js进行打包,这样的做法是可以的,但细想不就和上面的ts-loader一样的情况了吗?

只要开发过基于webpack的现代化前端项目的同学,或多或少都看到过babel-loader的身影,他是个什么东西呢?先说结论吧,babel-loader是webpack和babel(由@babel/core和一堆预置集preset、插件plugins组合)的桥梁。

根据这个图,同学可能觉得这不是和ts-loader的架构很像吗?webpack启动,遇到入口ts,匹配到babel-loader,babel-loader交给babel处理,处理完毕,回到webpack打包。但是使用babel进行ts处理,比起ts-loader更加高效。而关于这块的说明,我更加推荐读者阅读这篇文章TypeScript 和 Babel:美丽的结合 - 知乎 (zhihu.com),简单来讲:

警告!有一个震惊的消息,你可能想坐下来好好听下。

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

是的,它删除了所有 TypeScript,将其转换为“常规的” JavaScript,并继续以它自己的方式愉快处理。

这听起来很荒谬,但这种方法有两个很大的优势。

第一个优势:️️闪电般快速️。

大多数 Typescript 开发人员在开发/监视模式下经历过编译时间长的问题。你正在编写代码,保存一个文件,然后...它来了...再然后...最后,你看到了你的变更。哎呀,错了一个字,修复,保存,然后...啊。它只是慢得令人烦恼并打消你的势头。

很难去指责 TypeScript 编译器,它在做很多工作。它在扫描那些包括 node_modules 在内的类型定义文件(*.d.ts),并确保你的代码正确使用。这就是为什么许多人将 Typescript 类型检查分到一个单独的进程。然而,Babel + TypeScript 组合仍然提供更快的编译,这要归功于 Babel 的高级缓存和单文件发射架构。

让我们来搭建一个项目来复习这一过程吧:

mkdir webpack-babel-loader-demo && cd webpack-babel-loader-demo
yarn init
yarn add -D webpack webpack-cli
yarn add -D babel-loader
yarn add -D @babel/core
yarn add -D @babel/preset-env @babel/preset-typescript
yarn add -D @babel/plugin-proposal-class-properties @babel/plugin-proposal-object-rest-spread

package.json

{
"name": "webpack-babel-loader-demo",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"scripts": {
"build": "webpack --config webpack.config.js"
},
"devDependencies": {
"@babel/core": "^7.18.13",
"@babel/plugin-proposal-class-properties": "^7.18.6",
"@babel/plugin-proposal-object-rest-spread": "^7.18.9",
"@babel/preset-env": "^7.18.10",
"@babel/preset-typescript": "^7.18.6",
"babel-loader": "^8.2.5",
"webpack": "^5.74.0",
"webpack-cli": "^4.10.0"
}
}

webpack.config.js

const {resolve} = require('path');
module.exports = {
entry: './src/index.ts',
output: {
path: resolve(__dirname, './dist'),
filename: "index.js"
},
resolve: {
// webpack 默认只处理js、jsx等js代码
// 为了防止在import其他ts代码的时候,出现
// " Can't resolve 'xxx' "的错误,需要特别配置
extensions: ['.js', '.jsx', '.ts', '.tsx']
},
module: {
rules: [
{
test: /\.ts/,
loader: "babel-loader"
}
]
}
};

src/index.ts

import {hello} from "./utils";
interface User {
name: string;
age: number;
}
const userToString = (user: User) => `${user.name}@${user.age}${hello()}`;
export {userToString, User};

src/utils.ts

export const hello = () => {
return 'hello';
}

完成上述package.json、webpack.config.js、src源代码三个部分,我们可以开始运行yarn build,但实际上会报错:

ERROR in ./src/index.ts
Module build failed (from ./node_modules/babel-loader/lib/index.js):
SyntaxError: /Users/w4ngzhen/Projects/web-projects/webpack-babel-loader-demo/src/index.ts: Unexpected reserved word 'interface'. (1:0) > 1 | interface User {
| ^
2 | name: string;
3 | age: number;
4 | }
at instantiate (/Users/w4ngzhen/Projects/web-projects/webpack-babel-loader-demo/node_modules/@babel/parser/lib/index.js:72:32)

出现了语法的错误,报错的主要原因在于没有把整个babel处理ts的链路打通。目前的链路是:webpack找到入口ts文件,匹配上babel-loader,babel-loader交给@babel/core,@babel/core处理ts。由于我们没有给@babel/core配置plugin、preset,所以导致了babel还是以默认的js角度来处理ts代码,所以有语法报错。此时,我们需要添加.babelrc文件来指明让babel加载处理ts代码的插件:

.babelrc

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

完成配置以后,我们再次运行yarn build,编译通过,但是在dist下的index.js却是空白的!

问题:babel-loader编译后,输出js内容空白

如果按照上述的配置以后,我们能够成功编译但是却发现,输出的js代码是空白的!原因在于:我们编写的js代码,是按照类库的模式进行编写(在indexjs中只有导出一些函数却没有实际的使用),且webpack打包的时候,没有指定js代码的编译为什么样子的库。

假如我们在index中编写一段具有副作用的代码:

import {hello} from "./utils";
interface User {
name: string;
age: number;
}
const userToString = (user: User) => `${user.name}@${user.age}${hello()}`; // 具备副作用:在id=app的元素上添加监听
document
.querySelector('#app')
.addEventListener('click', () => {}) export {userToString, User};

此时我们使用生产模式(mode: 'production')来编译,会发现dist/index.js的内容如下:

(() => {
"use strict";
document.querySelector("#app").addEventListener("click", (function () {
}));
})();

会发现只有副作用代码,但是userToString相关的代码完全被剔除了!这时候,可能有读者会说,我导出的代码有可能别人会使用,你凭什么要帮我剔除?其实,因为webpack默认是生成项目使用的js,也就是做打包操作,他的目的是生成当前项目需要的js。在我们这个示例中,在没有写副作用之前,webpack认为打包是没有意义的,因为只有导出方法,却没有使用。那么,如果让webpack知道,我们需要做一个类库呢?在webpack中配置library字段即可:

const {resolve} = require('path');
module.exports = {
entry: './src/index.ts',
mode: 'production',
output: {
// ... ...
library: {
// 配置library字段的相关配置,这里我们配置为commonjs2
// 至于这块配置的意义,读者需要自行学习~
type: 'commonjs2',
},
},
// ... ...
};

tsc与babel编译的差异

现在我们先编写一个简单错误代码

interface User {
name: string;
age: number;
}
// user.myName并没有在User接口中提供
const userToString = (user: User) => `${user.myName}@${user.age}`;
export {userToString, User};

在这个示例中,我们试图访问在User类型中不存在的myName字段。

ts-loader

前面我们提到了ts-loader内部调用的是tsc作为编译器,我们尝试运行基于ts-loader的webpack配置进行打包该模块,会发现报错:

... ...
TS2551: Property 'myName' does not exist on type 'User'. Did you mean 'name'?
ts-loader-default_e3b0c44298fc1c14 webpack 5.74.0 compiled with 1 error in 2665 ms
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

可以看得出来,tsc帮助我们提示了类型错误的地方,user这个类型并没有对应的myName字段。

babel-loader

我们切换一下到babel-loader对该ts文件进行编译,居然发现编译可以直接成功!并且,我们检查编译好的js代码,会发现这部分:

// dist/index.js
(() => {
"use strict";
// ... ...
var r = function (e) {
// 注意这个地方:依然在使用myName
return "".concat(e.myName, "@").concat(e.age);
};
module.exports = o;
})();

编译好的js代码就在直接使用myName字段。为什么类型检查失效了?还记得我们前面提到的babel怎么处理ts的?

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

是的,它删除了所有 TypeScript,将其转换为“常规的” JavaScript,并继续以它自己的方式愉快处理。

是的,babel并没有进行类型检查,而是将各种类型移除掉以达到快速完成编译的目的。那么问题来了,我们如何让babel进行类型判断呢?实际上,我们没有办法让babel进行类型判断,必须要借助另外的工具进行。那为什么我们的IDE却能够现实ts代码的错误呢?因为IDE帮助我们进行了类型判断。

主流IDE对TypeScript的类型检查

不知道有没有细心的读者在使用IDEA的时候,发现一个ts项目的IDEA右下角展示了typescript:

VSCode也能看到类似:

在同一台电脑上,甚至发现IDEA和VSCode的typescript版本都还不一样(4.7.4和4.7.3)。这是怎么一回事呢?实际上,IDE检测到你所在的项目是一个ts项目的时候(或包含ts文件),就会自动的启动一个ts的检测服务,专门用于所在项目的ts类型检测。这个ts类型检测服务,是通过每个IDE默认情况下自带的typescript中的tsc进行类型检测。

但是,我们可以全局安装(npm -g)或者是为每个项目单独安装typescript,然后就可以让IDE选择启动独立安装的typescript。比如,我们在本项目中,安装一个特定版本的ts(版本4.7.2):

yarn add -D typescript@4.7.2

在IDEA中,设置 - Languages & Frameworks - TypeScript中,就可以选择IDEA启动的4.7.2版本的TypeScript为我们项目提供类型检查(注意看选项中有一个Bundled的TS,版本是4.7.4,就是默认的):

IDE之所以能够在对应的代码位置展示代码的类型错误,流程如下:

但是,ts类型检查也要有一定的依据。譬如,有些类型定义的文件从哪里查找,是否允许较新的语法等,这些配置依然是由tsconfig.json来提供的,但若未提供,则IDE会使用一份默认的配置。如果要进行类型检测的自定义配置,则需要提供tsconfig.json。

还记得我们前面的ts-loader吗?在代码编译期,ts-loader调用tsc,tsc读取项目目录下的tsconfig.json配置。而咱们编写代码的时候,又让IDE的ts读取该tsconfig.json配置文件进行类型检查。

对于ts-loader项目体系来说,ts代码编译和ts的类型检测如下:

然而,对于babel-loader项目体系就不像ts-loader那样了:

在babel-loader体系中,代码的编译只取决于babel部分的处理,根类型没有根本的关系,而类型检查使用到的tsconfig和tsc则只作用在类型检查的部分,根ts代码编译没有任何关系。

【长文详解】TypeScript、Babel、webpack以及IDE对TS的类型检查的更多相关文章

  1. 万字长文详解HiveSQL执行计划

    Hive SQL的执行计划描述SQL实际执行的整体轮廓,通过执行计划能了解SQL程序在转换成相应计算引擎的执行逻辑,掌握了执行逻辑也就能更好地把握程序出现的瓶颈点,从而能够实现更有针对性的优化.此外还 ...

  2. 万字长文详解HBase读写性能优化

    一.HBase 读优化 1. HBase客户端优化 和大多数系统一样,客户端作为业务读写的入口,姿势使用不正确通常会导致本业务读延迟较高实际上存在一些使用姿势的推荐用法,这里一般需要关注四个问题: 1 ...

  3. WebApi 接口返回值不困惑:返回值类型详解。IHttpActionResult、void、HttpResponseMessage、自定义类型

    首先声明,我还没有这么强大的功底,只是感觉博主写的很好,就做了一个复制,请别因为这个鄙视我,博主网址:http://www.cnblogs.com/landeanfen/p/5501487.html ...

  4. 详解Linux目录(目录树详细解释)

    给大家一篇关于Linux目录 方面的详细说明,好好读一下! Linux目录详解(RHEL5.4) linux有四种基本文件系统类型:--普通文件:如文本文件.c语言源代码.shell脚本等,可以用ca ...

  5. Linux 进程间通讯详解二

    消息队列 --消息队列提供了本机上从一个进程向另外一个进程发送一块数据的方法 --每个数据块都被认为有一个类型,接收者进程接收的数据块可以有不同的类型值 --消息队列也有管道一样的不足,就是每个消息的 ...

  6. C++11 并发指南六(atomic 类型详解三 std::atomic (续))

    C++11 并发指南六( <atomic> 类型详解二 std::atomic ) 介绍了基本的原子类型 std::atomic 的用法,本节我会给大家介绍C++11 标准库中的 std: ...

  7. iOS开发——实用技术OC篇&事件处理详解

    事件处理详解 一:事件处理 事件处理常见属性: 事件类型 @property(nonatomic,readonly) UIEventType     type; @property(nonatomic ...

  8. C# 中4个访问符和8个修饰符详解

    4个访问修饰符(是添加到类.结构或成员声明的关键字) Public:公有的,是类型和类型成员的访问修饰符.对其访问没有限制. Internal:内部的,是类型和类型成员的访问修饰符.同一个程序集中的所 ...

  9. python 3.x 爬虫基础---http headers详解

    前言 上一篇文章 python 爬虫入门案例----爬取某站上海租房图片 中有对headers的讲解,可能是对爬虫了解的不够深刻,所以老觉得这是一项特别简单的技术,也可能是简单所以网上对爬虫系统的文档 ...

随机推荐

  1. 【Java面试】介绍下Spring IoC的工作流程

    Hi,我是Mic 一个工作了4年的粉丝,在面试的时候遇到一个这样的问题. "介绍一下Spring IOC的工作流程" 他说回答得不是很好,希望我能帮他梳理一下. 关于这个问题,我们 ...

  2. 探索链路追踪在.NET6工业物联网项目的应用

    ExploringIoTDistributedTracingNet6 如果觉得有用,请留言学到了. 已经会了的老哥,请留言就这? 可能遇到的问题 工业物联网项目自上而下一般分为ERP.Mes.SCAD ...

  3. Redis如何实现多可用区?

    在如今的业务场景下,高可用性要求越来越高,核心业务跨可用区已然成为标配.腾讯云数据库高级工程师刘家文结合腾讯云数据库的内核实战经验,给大家分享Redis是如何实现多可用区,内容包含Redis主从版.集 ...

  4. Tapdata 实时数据融合平台解决方案(五):落地

    作者介绍:TJ,唐建法,Tapdata 钛铂数据 CTO,MongoDB中文社区主席,原MongoDB大中华区首席架构师,极客时间MongoDB视频课程讲师. 通过前面几篇文章,我们从企业数据整合与分 ...

  5. java中的内存划分和一个数组的内存图

    内存概述 内存是计算机中的重要原件,临时存储区域,作用是运行程序.我们编写的程序是存放在硬盘中的,在硬盘中的程序是不会运行的,必须放进内存中才能运行,运行完毕后会清空内存   Java虚拟机要运行程序 ...

  6. SpringCloudGateway微服务网关实战与源码分析 - 中

    实战 路由过滤器工厂 路由过滤器允许以某种方式修改传入的HTTP请求或传出的HTTP响应.路由过滤器的作用域是特定的路由.SpringCloud Gateway包括许多内置的GatewayFilter ...

  7. (一)java基础篇-----认识java

    1.简单介绍java起源: 1995年,詹姆斯-高斯林在sun公司开发出java编程语言.到2010年,sun公司被Oracle公司收购,而詹姆斯-高斯林也离开了Oracle公司.所以,如今想要安转j ...

  8. 使用开源Cesium+Vue实现倾斜摄影三维展示

    准备工作 VUE开发工具:Visual studio Code 倾斜摄影转换工具:CesiumLab-下载地址:http://www.cesiumlab.com/ 三维显示:Cesium,api参考网 ...

  9. AOV网的实现(数据结构)

    #include <stdio.h> #include <stdlib.h> #include <string.h>//我这里的头以及尾巴与书上的不一样. int ...

  10. AtCoder Beginner Contest 247 F - Cards // dp + 并查集

    原题链接:F - Cards (atcoder.jp) 题意: 给定N张牌,每张牌正反面各有一个数,所有牌的正面.反面分别构成大小为N的排列P,Q. 求有多少种摆放方式,使得N张牌朝上的数字构成一个1 ...