本页内容
配置 TypeScript
Deno 在 TypeScript 和 JavaScript 上的平等对待彰显了其灵活性。无论您是从 JavaScript 过渡到 TypeScript,反之亦然,Deno 都提供了简化这一过程的功能。
类型检查 JavaScript Jump to heading
您可能希望让您的 JavaScript 更具类型安全性,而无需到处添加类型注解。Deno 支持使用 TypeScript 类型检查器来对 JavaScript 进行类型检查。您可以通过向文件添加 check JavaScript pragma 来标记单个文件
// @ts-check
这将使类型检查器推断 JavaScript 代码的类型信息,并将其发现的任何问题作为诊断问题报告。
通过提供一个配置文件,并将 check JS 选项设置为 true,如下所示,可以为程序中的所有 JavaScript 文件开启这些检查。然后在命令行运行时使用 --config 选项。
{
"compilerOptions": {
"checkJs": true
}
}
在 JavaScript 中使用 JSDoc Jump to heading
在类型检查 JavaScript 或将 JavaScript 导入 TypeScript 时,JSDoc 注解可以提供超出代码本身可推断的额外类型信息。如果您按照支持的 TypeScript JSDoc 在代码中内联注解,Deno 会无缝支持此功能。
例如,要设置数组的类型,请使用以下 JSDoc 注释
/** @type {string[]} */
const a = [];
跳过类型检查 Jump to heading
您可能有一些正在试验的 TypeScript 代码,其语法有效但并非完全类型安全。您可以通过传递 --no-check 标志来绕过整个程序的类型检查。
您也可以通过使用 nocheck pragma 来跳过整个文件的类型检查,包括已启用 check JS 的 JavaScript 文件。
// @ts-nocheck
将 JS 文件重命名为 TS 文件 Jump to heading
TypeScript 文件受益于 TypeScript 编译器能够对您的代码进行更彻底的安全检查。这通常被称为严格模式。当您将 .js 文件重命名为 .ts 文件时,您可能会看到以前 TypeScript 无法检测到的新类型错误。
在 Deno 中配置 TypeScript Jump to heading
TypeScript 提供了许多配置选项,这对于刚开始使用 TS 的人来说可能令人生畏。Deno 旨在简化 TypeScript 的使用,而不是让您淹没在无数设置中。Deno 将 TypeScript 配置为开箱即用,直接可用。无需额外的配置麻烦!
但是,如果您确实想更改 TypeScript 编译器选项,Deno 允许您在 deno.json 文件中进行更改。在命令行中提供路径,或使用默认路径。例如
deno run --config ./deno.json main.ts
如果您正在创建需要配置文件的库,请记住所有使用您的 TS 模块的消费者也都需要该配置文件。此外,配置文件中可能存在使其他 TypeScript 模块不兼容的设置。
TS 编译器选项 Jump to heading
下表列出了可以更改的编译器选项、它们在 Deno 中的默认值以及有关该选项的任何其他备注
| 选项 | 默认值 | 备注 |
|---|---|---|
allowJs |
true |
这几乎从不需要更改 |
allowUnreachableCode |
false |
|
allowUnusedLabels |
false |
|
checkJs |
false |
如果为 true,则会使 TypeScript 对 JavaScript 进行类型检查 |
jsx |
"react" |
|
jsxFactory |
"React.createElement" |
|
jsxFragmentFactory |
"React.Fragment" |
|
keyofStringsOnly |
false |
|
lib |
[ "deno.window" ] |
此默认值根据 Deno 中的其他设置而异。如果提供,它会覆盖默认值。有关更多信息,请参阅下文。 |
noErrorTruncation |
false |
|
noFallthroughCasesInSwitch |
false |
|
noImplicitAny |
true |
|
noImplicitOverride |
true |
|
noImplicitReturns |
false |
|
noImplicitThis |
true |
|
noImplicitUseStrict |
true |
|
noStrictGenericChecks |
false |
|
noUnusedLocals |
false |
|
noUnusedParameters |
false |
|
noUncheckedIndexedAccess |
false |
|
reactNamespace |
React |
|
strict |
true |
|
strictBindCallApply |
true |
|
strictFunctionTypes |
true |
|
strictPropertyInitialization |
true |
|
strictNullChecks |
true |
|
suppressExcessPropertyErrors |
false |
|
suppressImplicitAnyIndexErrors |
false |
|
useUnknownInCatchVariables |
true |
有关编译器选项及其如何影响 TypeScript 的完整列表,请参阅 TypeScript 手册。
使用“lib”属性 Jump to heading
如果您正在处理一个将代码发布到多个运行时(例如浏览器)的项目,您可以通过 compilerOptions 中的“lib”属性来调整默认类型。
用户感兴趣的内置库
"deno.ns"- 这包括所有自定义的Deno全局命名空间 API 以及 Deno 对import.meta的添加。这通常不应与其他库或全局类型冲突。"deno.unstable"- 这包括新增的不稳定Deno全局命名空间 API。"deno.window"- 这是检查 Deno 主运行时脚本时使用的“默认”库。它包括"deno.ns"以及内置于 Deno 中的扩展的其他类型库。该库将与"dom"和"dom.iterable"等标准 TypeScript 库冲突。"deno.worker"- 这是检查 Deno web worker 脚本时使用的库。有关 web worker 的更多信息,请查看 类型检查 Web Workers。"dom.asynciterable"- TypeScript 目前不包含 Deno(以及一些浏览器)实现的 DOM 异步迭代器,因此我们在 TypeScript 中可用之前自行实现了它。
这些是默认未启用但对于编写也打算在其他运行时中运行的代码有用的常见库
"dom"- TypeScript 附带的主要浏览器全局库。类型定义在许多方面与"deno.window"冲突,因此如果使用"dom",则考虑仅使用"deno.ns"来暴露 Deno 特定的 API。"dom.iterable"- 浏览器全局库的迭代扩展。"scripthost"- Microsoft Windows 脚本主机的库。"webworker"- 浏览器中 web worker 的主库。与"dom"类似,这将与"deno.window"或"deno.worker"冲突,因此请考虑仅使用"deno.ns"来暴露 Deno 特定的 API。"webworker.importscripts"- 暴露 web worker 中importScripts()API 的库。"webworker.iterable"- 为 web worker 中的对象添加迭代器的库。现代浏览器支持此功能。
同时面向 Deno 和浏览器 Jump to heading
您可能希望编写可以在 Deno 和浏览器中无缝运行的代码。在这种情况下,您需要在使用任何特定于某个环境的 API 之前有条件地检查执行环境。在这种情况下,典型的 compilerOptions 配置可能如下所示
{
"compilerOptions": {
"lib": ["dom", "dom.iterable", "dom.asynciterable", "deno.ns"]
}
}
这应该允许 Deno 正确地对大多数代码进行类型检查。
如果您期望在 Deno 中使用 --unstable 标志运行代码,那么您也应该将该库添加到其中
{
"compilerOptions": {
"lib": [
"dom",
"dom.iterable",
"dom.asynciterable",
"deno.ns",
"deno.unstable"
]
}
}
通常,当您在 TypeScript 中使用 "lib" 选项时,您还需要包含一个“es”库。在 "deno.ns" 和 "deno.unstable" 的情况下,它们在引入时会自动包含 "esnext"。
如果您收到类似 cannot find document or HTMLElement 的类型错误,则很可能是您使用的库依赖于 DOM。这对于设计为在浏览器和服务器端运行的包来说很常见。默认情况下,Deno 只包含直接支持的库。假设该包在运行时正确识别了它运行的环境,则使用 DOM 库对代码进行类型检查是“安全”的。
类型与类型声明 Jump to heading
Deno 遵循 无非标准模块解析 的设计原则。当 TypeScript 检查一个文件时,它只关注其类型。相比之下,tsc 编译器采用复杂的逻辑来解析这些类型。默认情况下,tsc 期望带有扩展名(例如,.ts、.d.ts 或 .js)的模糊模块说明符。然而,Deno 处理的是显式说明符。
有趣的地方来了:想象一下您想使用一个已经转译为 JavaScript 的 TypeScript 文件,以及它的类型定义文件(mod.js 和 mod.d.ts)。如果您将 mod.js 导入 Deno,它会严格遵循您的请求并导入 JavaScript 文件。但问题是:您的代码将不会像 TypeScript 同时考虑 mod.d.ts 文件和 mod.js 文件那样进行彻底的类型检查。
为了解决这个问题,Deno 提供了两种解决方案,每种方案都适用于特定的场景
作为导入者:如果您知道应该应用于 JavaScript 模块的类型,您可以通过显式指定类型来增强类型检查。
作为提供者:如果您是模块的提供者或宿主,那么所有使用它的消费者都可以从中受益,而无需担心类型解析。
导入时提供类型 Jump to heading
如果您正在使用 JavaScript 模块,并且您已经创建了类型(一个 .d.ts 文件)或以其他方式获得了您想要使用的类型,您可以使用 @ts-types 编译器提示,指示 Deno 在类型检查时使用该文件,而不是 JavaScript 文件。
例如,如果您有一个 JavaScript 模块 coolLib.js 和一个单独的 coolLib.d.ts 文件,您将像这样导入它
// @ts-types="./coolLib.d.ts"
import * as coolLib from "./coolLib.js";
当您对 coolLib 进行类型检查并在您的文件中使用它时,来自 coolLib.d.ts 的 TypeScript 类型定义将优先于检查 JavaScript 文件。
过去 @ts-types 指令被称为 @deno-types。此别名仍然有效,但不再推荐使用。请使用 @ts-types。
托管时提供类型 Jump to heading
如果您可以控制模块的源代码或文件在 Web 服务器上的托管方式,有两种方法可以让 Deno 知道特定模块的类型(这将不需要导入者采取任何特殊操作)。
@ts-self-types Jump to heading
如果您提供一个 JavaScript 文件,并且想提供一个包含此文件类型的声明文件,您可以在 JS 文件中指定一个 @ts-self-types 指令,指向该声明文件。
例如,如果您制作一个 coolLib.js 库,并将其类型定义写入 coolLib.d.ts,则 ts-self-types 指令将如下所示
// @ts-self-types="./coolLib.d.ts"
// ... the rest of the JavaScript ...
X-TypeScript-Types Jump to heading
Deno 支持远程模块的头信息,该头信息指示 Deno 在何处查找给定模块的类型。例如,https://example.com/coolLib.js 的响应可能如下所示
HTTP/1.1 200 OK
Content-Type: application/javascript; charset=UTF-8
Content-Length: 648
X-TypeScript-Types: ./coolLib.d.ts
当看到此头信息时,Deno 将尝试检索 https://example.com/coolLib.d.ts 并在对原始模块进行类型检查时使用它。
使用环境或全局类型 Jump to heading
总的来说,最好在 Deno 中使用模块/UMD 类型定义,其中模块明确导入其依赖的类型。模块化类型定义可以通过类型定义中的 declare global 来表达 全局范围的扩充。例如
declare global {
var AGlobalString: string;
}
这将在导入类型定义时使 AGlobalString 在全局命名空间中可用。
但在某些情况下,当利用其他现有类型库时,可能无法利用模块化类型定义。因此,有办法在类型检查程序时包含任意类型定义。
三斜杠指令 Jump to heading
此选项将类型定义与代码本身耦合。通过在 TS 文件(不是 JS 文件!)中,在模块类型附近添加一个三斜杠 types 指令,类型检查文件将包含类型定义。例如
/// <reference types="./types.d.ts" />
所提供的说明符与 Deno 中的任何其他说明符一样被解析,这意味着它需要一个扩展名,并且是相对于引用它的模块的。它也可以是一个完全限定的 URL
/// <reference types="https://deno.land/x/pkg@1.0.0/types.d.ts" />
在 deno.json 中提供“types” Jump to heading
另一个选项是在 deno.json 的 "compilerOptions" 中提供一个 "types" 值。例如
{
"compilerOptions": {
"types": [
"./types.d.ts",
"https://deno.land/x/pkg@1.0.0/types.d.ts",
"/Users/me/pkg/types.d.ts"
]
}
}
与上面的三斜杠引用一样,"types" 数组中提供的说明符将像 Deno 中的其他说明符一样被解析。对于相对说明符,它将相对于配置文件路径进行解析。请务必通过指定 --config=path/to/file 标志来告诉 Deno 使用此文件。
类型检查 Web Workers Jump to heading
当 Deno 在 web worker 中加载 TypeScript 模块时,它将自动根据 Deno web worker 库对模块及其依赖项进行类型检查。这在 deno check 或编辑器等其他上下文中可能会带来挑战。有几种方法可以指示 Deno 使用 worker 库而不是标准的 Deno 库。
三斜杠指令 Jump to heading
此选项将库设置与代码本身耦合。通过在 worker 脚本的入口文件顶部附近添加以下三斜杠指令,Deno 现在将它作为 Deno worker 脚本进行类型检查,无论模块如何分析
/// <reference no-default-lib="true" />
/// <reference lib="deno.worker" />
第一个指令确保不使用其他默认库。如果省略此指令,您将得到一些冲突的类型定义,因为 Deno 会尝试同时应用标准 Deno 库。第二个指令指示 Deno 应用内置的 Deno worker 类型定义以及依赖库(如 "esnext")。
这有一个缺点,就是它使代码对其他非 Deno 平台(如 tsc)的可移植性降低,因为只有 Deno 内置了 "deno.worker" 库。
在 deno.json 中提供“lib”设置 Jump to heading
您可以在 deno.json 文件中提供一个 "lib" 选项,以指示 Deno 使用库文件。例如
{
"compilerOptions": {
"target": "esnext",
"lib": ["deno.worker"]
}
}
然后,在运行 deno 子命令时,您需要传递 --config path/to/file 参数,或者如果您正在使用利用 Deno 语言服务器的 IDE,请设置 deno.config。
如果您还有非 worker 脚本,您要么需要省略 --config 参数,要么拥有一个配置为满足非 worker 脚本需求的配置文件。
重要提示 Jump to heading
类型声明语义 Jump to heading
类型声明文件(.d.ts 文件)遵循与 Deno 中其他文件相同的语义。这意味着声明文件被假定为模块声明(UMD 声明),而不是环境/全局声明。Deno 如何处理环境/全局声明是不可预测的。
此外,如果类型声明导入了其他内容,例如另一个 .d.ts 文件,其解析遵循 Deno 的正常导入规则。对于许多在网络上生成和可用的 .d.ts 文件,它们可能与 Deno 不兼容。
esm.sh 是一个 CDN,默认提供类型声明(通过 X-TypeScript-Types 头)。可以通过在导入 URL 后附加 ?no-dts 来禁用它
import React from "https://esm.sh/react?no-dts";
类型检查 JavaScript 时的行为 Jump to heading
当您在 Deno 中将 JavaScript 代码导入 TypeScript 时,即使您已将 checkJs 设置为 false(这是 Deno 的默认行为),TypeScript 编译器仍会分析 JavaScript 模块。它会尝试推断该模块导出的形状,以验证 TypeScript 文件中的导入。
通常,导入标准 ES 模块时这不是问题。但是,在某些情况下,TypeScript 的分析可能会失败,例如,对于具有特殊打包或全局 UMD(Universal Module Definition)模块。当遇到这种情况时,最好的方法是使用前面提到的一种方法提供某种形式的类型信息。
内部 Jump to heading
虽然不要求理解 Deno 的内部工作原理就能很好地利用 TypeScript 与 Deno,但了解其工作原理会有所帮助。
在执行或编译任何代码之前,Deno 通过解析根模块生成模块图,然后检测其所有依赖项,接着递归地检索和解析这些模块,直到所有依赖项都被检索完毕。
对于每个依赖项,有两个潜在的“槽位”被使用。一个是代码槽位,另一个是类型槽位。随着模块图的填充,如果模块是或可以被编译成 JavaScript 的东西,它会填充代码槽位,而只有类型的依赖项,如 .d.ts 文件,会填充类型槽位。
当模块图构建完成并需要对图进行类型检查时,Deno 会启动 TypeScript 编译器并向其提供需要潜在地编译成 JavaScript 的模块名称。在此过程中,TypeScript 编译器将请求额外的模块,Deno 将查看依赖项的槽位,如果类型槽位已填充,则优先提供类型槽位,然后再提供代码槽位。
这意味着当您导入 .d.ts 模块,或者使用上述解决方案之一为 JavaScript 代码提供替代类型模块时,在解析模块时,提供的就是这些替代类型。