Skip to content

TS 实际应用

以 Vite 为切入点,了解围绕 TS 需要准备的东西,以及 TS 是如何生效的

Vite

当我们使用 Vite 脚手架生成一个项目目录时,会发现和 TS 相关的文件不少(这在一个空业务代码的初始项目里很扎眼)

bash
├── tsconfig.json            # 基础配置
├── tsconfig.app.json        # 应用代码配置
├── tsconfig.node.json       # 工具链配置
└── src/
    └── vite-env.d.ts        # 环境类型声明

安装 TS 环境

让我们从源头开始:

首先看一下 package.json 中关于 ts 依赖,发现只安装了 1 个相关的依赖包 typescript

json
{
  "devDependencies": {
    "typescript": "^5.7.2",
    "vite": "^6.0.5",
  }
}

官方文档:TypeScript Tooling in 5 minutes

There are two main ways to add TypeScript to your project:

- Via npm (the Node.js package manager)
- By installing TypeScript’s Visual Studio plugins

For npm users:

> npm install -g typescript

Visual Studio 2017 and Visual Studio 2015 Update 3 include TypeScript language support by default but does not include the TypeScript compiler, tsc.
If you didn’t install TypeScript with Visual Studio, you can still download it.

在一个项目中引入 ts,需要 2 个东西:

  1. npm 依赖,提供命令操作 ts 代码 or 被其他构建工具操作,提供一种更灵活底层的使用方式
  2. 代码编辑器 (VSCode) Plugin 支持实时编写的类型检查提醒

而 1 个 npm 依赖 typescript,同时包含多种功能,主要是 2 个显性的功能:1. 扫描代码进行类型检查 2. 编译(通过 npx tsc xxx 根据 ts 输出 js

正如官方文档所说,VSCode 已经内置的 TS 的类型检查功能,但是不包括 TypeScript compiler, tsc 编译器功能

因此如果我们的项目不需要编译器(根据 ts 输出为 js)功能时,由于 VSCode 已经内置了类型检查功能

可以不安装 npm 包

也就意味着👆官方文档指出的引入 2 个东西都不需要了

你可能会说,现实中我们不管写 Nodejs 脚本 or Web,都只能识别 js,那就必须要安装 npm 来编译了🤔

是的 Web 必须要使用 JS,但是 Nodejs 在 v23 之后可以识别 ts,而不需要显式的输出 js 来给 Nodejs 使用

所以!当我们现在需要写一个 Nodejs 项目时,不再需要刻意去搭建 TS 环境,只需要在 VSCode 中创建 .ts 文件,开始 enjoy ts 的实时类型提示,最后直接 node a.ts 执行

Nodejs V22.6 run Ts by natively

node --experimental-strip-types test.ts

--experimental-strip-types --实验性-剥离-类型

Web 项目构建 ts

json
{
  "build": "tsc & vite build",
  "build-only": "vite build",
}

Vite 内部只会使用 ESBuild 的抹除 ts 语法功能,不会对 ts 进行类型检查

当需要在构建时确保 ts 的类型正确,应该手动执行 npx tsc (足够信任代码编辑器实时类型检查并即使修复的话,可以不执行)

编译时配置 tsconfig

TS 内部有默认的配置文件,也有内置的读取配置文件寻址逻辑,在一个项目里,不需要给构建工具 or 其他工具 显式的指定 tsconfig 文件,只需要在项目根目录创建一个 tsconfig.json 文件即可

配置详细理解

ts 的配置感觉在走 Webpack 的旧路,太复杂了 ...

TypeScript is an excellent tool in monorepos, allowing teams to safely add types to their JavaScript code. While there is some complexity to getting set up,

Turorepo 官方文档 - Sharing TypeScript configuration

👇 Vite 项目的 tsconfig.json 主文件没有配置内容,而是模块化区分了 nodejsweb 的 ts 不同配置

这也是现代前端项目需要考虑的问题:一个项目里有些代码运行在 nodejs 环境(编译时),有些代码则运行在浏览器环境(运行时)

json
{
  // 指定要包含在编译中的文件列表。在这里,它是一个空数组,因为我们使用 "references" 来引用其他配置文件。
  "files": [],
  // 指定要引用的其他 TypeScript 配置文件。这允许我们将配置拆分为多个文件,以便更好地组织和管理。
  "references": [
    // Web 应用程序代码的配置。
    { "path": "./tsconfig.app.json" },
    // Node.js 环境代码的配置。
    { "path": "./tsconfig.node.json" }
  ]
}
tsconfig.app.json 逐行注释
json
{
  "compilerOptions": {
    // 指定用于存储增量编译信息的文件的路径。这有助于加快后续的构建速度。
    "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
    // 指定 ECMAScript 目标版本。ES2020 是一个较新的版本,支持现代 JavaScript 语法。
    "target": "ES2020",
    // 使用 `define` 属性来定义类字段。
    "useDefineForClassFields": true,
    // 指定模块代码生成方式。ESNext 允许使用最新的 JavaScript 模块语法。
    "module": "ESNext",
    // 指定要包含在编译中的库文件。
    "lib": [
      "ES2020", // 包含 ES2020 的类型定义。
      "DOM", // 包含 DOM API 的类型定义,用于浏览器环境。
      "DOM.Iterable" // 包含 DOM 中可迭代对象的类型定义。
    ],
    // 跳过对声明文件(`.d.ts`)的类型检查,以加快编译速度。
    "skipLibCheck": true,

    /* Bundler mode */
    "moduleResolution": "bundler", // 使用 bundler 模式进行模块解析,与 Vite 等打包工具兼容。
    "allowImportingTsExtensions": true, // 允许在导入语句中使用 `.ts` 扩展名。
    "isolatedModules": true, // 将每个文件作为单独的模块进行编译,与 Vite 的工作方式兼容。
    "moduleDetection": "force", // 强制启用模块检测。
    "noEmit": true, // 不生成输出文件(`.js`),因为 Vite 会处理代码的转换和打包。
    "jsx": "react-jsx", // 支持 React 的 JSX 语法,并使用新的 JSX 转换方式。

    /* Linting */
    "strict": true, // 启用所有严格类型检查选项,以提高代码质量。
    "noUnusedLocals": true, // 报告未使用的局部变量。
    "noUnusedParameters": true, // 报告未使用的函数参数。
    "noFallthroughCasesInSwitch": true, // 防止 switch 语句中的 case 穿透。
    "noUncheckedSideEffectImports": true // 如果导入的模块没有被使用,则发出错误。
  },
  // 指定要包含在编译中的文件或目录。这里指定了 `src` 目录下的所有 TypeScript 文件。
  "include": ["src"]
}
tsconfig.node.json 逐行注释
json
{
  "compilerOptions": {
    // 指定用于存储增量编译信息的文件的路径。
    "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
    // 指定 ECMAScript 目标版本。ES2022 是一个较新的版本,支持现代 JavaScript 语法。
    "target": "ES2022",
    // 指定模块代码生成方式。ESNext 允许使用最新的 JavaScript 模块语法。
    "module": "ESNext",
    // 指定要包含在编译中的库文件。
    "lib": [
      "ES2023" // 包含 ES2023 的类型定义。
    ],
    "skipLibCheck": true,

    /* Bundler mode */
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "isolatedModules": true,
    "moduleDetection": "force",
    "noEmit": true,

    /* Linting */
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noFallthroughCasesInSwitch": true,
    "noUncheckedSideEffectImports": true
  },
  // 指定要包含在编译中的文件或目录。这里指定了 `vite.config.ts`
  "include": ["vite.config.ts"]
}

👆 在 node.config 中省略了一些重复的注释,我们对比来看

配置项tsconfig.app.jsontsconfig.node.json区别说明
include["src"]["vite.config.ts"]编译范围不同,app 编译 src 目录,node 只编译 vite 配置文件
tsBuildInfoFile"./node_modules/.tmp/tsconfig.app.tsbuildinfo""./node_modules/.tmp/tsconfig.node.tsbuildinfo"文件名不同
targetES2020ES2022node 配置使用更新的 ES 版本
lib["ES2020", "DOM", "DOM.Iterable"]["ES2023"]app 包含浏览器 DOM 类型,node 只包含 ES2023 标准库
useDefineForClassFieldstrue不包含app 配置明确指定了类字段的定义行为
jsx"react-jsx"不包含app 支持 JSX 语法
- module: "ESNext"
- skipLibCheck: true
- moduleResolution: "bundler"
- allowImportingTsExtensions: true
- isolatedModules: true
- moduleDetection: "force"
- noEmit: true
- strict: true
- noUnusedLocals: true
- noUnusedParameters: true
- noFallthroughCasesInSwitch: true
- noUncheckedSideEffectImports: true
相同相同Linting、Bundler 相同

👇 大部分配置是相同的

json
// 指定模块代码生成方式。ESNext 允许使用最新的 JavaScript 模块语法。
"module": "ESNext",
// 跳过对声明文件(`.d.ts`)的类型检查,以加快编译速度。
"skipLibCheck": true,

/* Bundler mode */
"moduleResolution": "bundler", // 使用 bundler 模式进行模块解析,与 Vite 等打包工具兼容。
"allowImportingTsExtensions": true, // 允许在导入语句中使用 `.ts` 扩展名。
"isolatedModules": true, // 将每个文件作为单独的模块进行编译,与 Vite 的工作方式兼容。
"moduleDetection": "force", // 强制启用模块检测。
"noEmit": true, // 不生成输出文件(`.js`),因为 Vite 会处理代码的转换和打包。

/* Linting */
"strict": true, // 启用所有严格类型检查选项,以提高代码质量。
"noUnusedLocals": true, // 报告未使用的局部变量。
"noUnusedParameters": true, // 报告未使用的函数参数。
"noFallthroughCasesInSwitch": true, // 防止 switch 语句中的 case 穿透。
"noUncheckedSideEffectImports": true // 如果导入的模块没有被使用,则发出错误。

vite Some configuration fields

简化配置?

那么我们如果确实不喜欢项目根目录放太多文件的话,我们可以

  1. 把配置文件放到其他目录下以保证项目结构清晰
    1. 不再分文件而是写到一个文件里(暂没找到好的区分方法,因为 1 个配置文件无法根据 include 取分不同的配置,如 target 等)

env.d.ts

bash
├── tsconfig.json            # 基础配置
├── tsconfig.app.json        # 应用代码配置
├── tsconfig.node.json       # 工具链配置
└── src/
    └── vite-env.d.ts        # 环境类型声明

👆 解释了 tsconfig.json 为什么会有 3 个文件,现在需要理解另一个东西 .d.ts

ts
/// <reference types="vite/client" />

👆 这是一种.d.ts 类型声明文件的模块化写法(类似于 js 之间的import) - TS 模块化

vite Some configuration fields

  1. 提供了d.ts 全局处理 非 JS 文件 的模块(一般是忽略 or 手写)
  2. 提供了 vite 内置的环境变量,借助 importMeta 来实现运行时注入环境变量

环境变量使用-vite 官方文档

js
console.log(import.meta.env.BASE_URL)
console.log(import.meta.env.MODE)

👇 我们由此延伸一下构建工具里环境变量的原理

构建工具会在构建时扫描代码中使用到的地方,全部替换成常量

  • webpack 以 nodejs 语法为准 process.env.xxx,方便自身构建工具在 nodejs 环境使用,即在 Web 浏览器运行时的业务代码 编写时会出现 nodejs 的语法,从而让人有点割裂的感觉
  • vite 以 浏览器语法为准 import.meta.env.xxx,由构建工具处理这种语法(不困难,这在 nodejs 和 浏览器都认识), 注意原生的 import.meta. 并没有 env 这个对象。import.meta.url结合 new URL() 很好用,可以多了解
js
// webpack 环境变量 plugin
module.exports = {
  // ...
  plugins: [
    new webpack.DefinePlugin({
      'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development'),
      'process.env.API_URL': JSON.stringify(process.env.API_URL),
    }),
  ],
};
特性WebpackVite
访问方式process.env.VAR_NAMEimport.meta.env.VAR_NAME
插件DefinePluginEnvironmentPlugindotenv-webpack无需额外插件,内置支持
类型定义需要手动配置vite/client 提供类型定义
处理方式构建时替换构建时替换
规范不符合 ESM 规范符合 ESM 规范
全局变量污染存在避免
灵活性较低较高
配置复杂度较高较低