【TS】TypeScript配置详解【三】
简介
TypeScript 代码最终都会被编译成 JavaScript 代码来运行。这个编译的过程需要使用 TypeScript 编译器,我们可以为该编译器配置一些编译选项。
在 TypeScript 项目的根目录下执行 “tsc-init” 命令,快速创建一个 tsconfig.json 文件。该文件用于配置 TypeScript 编译项目时编译器所需的选项。下面是该配置文件中比较常见的属性:
{
"compilerOptions": {
"target": "es2016", // 编译生成的目标版本代码
// "lib": ["esnext","dom","dom.iterable","scripthost"], // 指定我们需要用到的库,也可以不配置,直接根据 target 来获取 /* Specify a set of bundled library declaration files that describe the target runtime environment. */
"jsx": "preserve", // jsx 的处理方式(保留原有的jsx格式)
"module": "commonjs", // 生成代码使用的模块化
"moduleResolution": "node10", // 按照 node 的模块解析规则
"baseUrl": "./", // 文件路径在解析时的基本url
// "paths": {"@/*":["src/*"]}, // 路径的映射设置,类似于webpack中的 alias
// "types": ["webpack-dev"], // 指定需要加载哪些 types 文件(默认都会进行加载)
"allowJs": true, // 允许在项目中导入 JavaScript 文件
"sourceMap": true, // 是否要生成 sourcemap 文件
"importHelpers": true, // 是否帮助导入一些需要的功能模块
"allowSyntheticDefaultImports": true, // 允许合成默认模块导出
"esModuleInterop": true, // 支持es module 和 commonjs 混合使用
"strict": true, // 打开所有的严格模式检查
"noImplicitAny": false, // 为具有隐含“any”类型的表达式和声明启用错误报告
"skipLibCheck": true, // 跳过对整个库的类型检测,而仅检测你用到的类型
"include": [ // 指定编译时包含的文件
"src/**/*.ts",
"src/**/*.vue",
"tests/**/*.ts",
"tests/**/*.tsx"
],
"exclude":["node_modules"] // 指定编译时应跳过的文件
}
}
根字段
files
指定要包含在程序中的文件的允许列表。如果找不到任何文件,则会发生错误。
{
"compilerOptions": {},
"files": [
"core.ts",
"sys.ts",
"types.ts",
"scanner.ts",
"parser.ts",
"utilities.ts",
"binder.ts",
"checker.ts",
"tsc.ts"
]
}
include
指定要包含在程序中的文件名或模式数组。 这些文件名相对于包含文件的目录进行解析。
例如通过vue create 脚手架创建的配置文件中 include 配置内容如下:
{
"include": [
"src/**/*.ts",
"src/**/*.tsx",
"src/**/*.vue",
"tests/**/*.ts",
"tests/**/*.tsx"
]
}
官网示例:
{
"include": ["src/**/*", "tests/**/*"]
}
匹配规则:
- *匹配零个或多个字符(不包括目录分隔符)
- ?匹配任意一个字符(不包括目录分隔符)
- **/匹配嵌套到任何级别的任何目录
如果模式中的最后一个路径段不包含文件扩展名或通配符,则将其视为一个目录,并包含该目录中支持扩展名的文件
exclude
指定解析 include 时应跳过的文件名或模式数组。
{
"exclude": [
"node_modules"
]
}
references
在 TypeScript 的 tsconfig.json 配置文件中,references 属性用于支持项目引用(Project References),这是 TypeScript 3.0 引入的一个功能,允许你将大型 TypeScript 项目分解为多个较小的、更易于管理的子项目(或称为“项目”)。
使用项目引用,你可以:
- 并行编译:每个子项目可以独立编译,从而加快大型项目的编译速度。
- 依赖管理:明确指定项目之间的依赖关系,确保编译顺序正确。
- 增量编译:只重新编译已更改的文件及其依赖项,而不是整个项目。
{
"compilerOptions": {
// ... 其他编译选项
},
"references": [
{ "path": "../project1" },
{ "path": "../project2" },
// ... 其他项目引用
]
}
其中,path 字段指定了被引用项目的相对路径或绝对路径。这个路径应该指向另一个包含 tsconfig.json 的目录。
CompilerOptions(编辑器选项)
这些选项构成了 TypeScript 配置的大部分内容,它涵盖了该语言的工作方式。
Type Checking(类型检查)
allowUnreachableCode(允许无法访问的代码)
- undefined:(默认)向编辑者提供建议作为警告
- true:无法访问的代码将被忽略
- false:引发有关无法访问代码的编译器错误
这些警告仅涉及由于使用 JavaScript 语法而无法访问的代码,例如:
function fn(n: number) {
if (n > 5) {
return true;
} else {
return false;
}
return true;
}
当设置"allowUnreachableCode": false时,上面就会出现报错:
Unreachable code detected.
这不会影响基于代码的错误,这些错误由于类型分析而似乎无法访问。
allowUnusedLabels(允许未使用的标签)
- undefined:(默认)向编辑者提供建议作为警告
- true:未使用的标签将被忽略
- false:引发有关未使用标签的编译器错误
标签在 JavaScript 中非常罕见,通常表示尝试编写对象文本:
function verifyAge(age: number) {
// Forgot 'return' statement
if (age > 18) {
verified: true;
// 报错:Unused label.
}
}
标签一般用于标识循环或 switch 语句中的特定位置。
break语句也可以与标签配合使用示例:
top:
for (var i = 0; i < 3; i++){
for (var j = 0; j < 3; j++){
if (i === 1 && j === 1) break top;
console.log('i=' + i + ', j=' + j);
}
}
// i=0, j=0
// i=0, j=1
// i=0, j=2
// i=1, j=0
上面代码为一个双重循环区块,break命令后面加上了top标签(注意,top不用加引号),满足条件时,直接跳出双层循环。如果break语句后面不使用标签,则只能跳出内层循环,进入下一次的外层循环。
continue语句也可以与标签配合使用示例:
top:
for (var i = 0; i < 3; i++){
for (var j = 0; j < 3; j++){
if (i === 1 && j === 1) continue top;
console.log('i=' + i + ', j=' + j);
}
}
// i=0, j=0
// i=0, j=1
// i=0, j=2
// i=1, j=0
// i=2, j=0
// i=2, j=1
// i=2, j=2
上面代码中,continue命令后面有一个标签名,满足条件时,会跳过当前循环,直接进入下一轮外层循环。如果continue语句后面不使用标签,则只能进入下一轮的内层循环。
alwaysStrict(始终严格)
确保在 ECMAScript 严格模式下解析文件,并为每个源文件发出“use strict”。
ECMAScript 严格模式是在 ES5 中引入的,它提供了对 JavaScript 引擎运行时的行为调整以提高性能,并抛出一组错误,而不是默默地忽略它们。
exactOptionalPropertyTypes(确切的可选属性类型)
在 TypeScript 的 tsconfig.json 配置文件中,exactOptionalPropertyTypes 是一个在 TypeScript 4.4 版本中引入的新特性。这个选项控制 TypeScript 是否将可选属性类型视为“确切的”或“非确切的”。
如下示例:
interface UserDefaults {
// The absence of a value represents 'system'
colorThemeOverride?: "dark" | "light";
}
如果不启用此规则,即 exactOptionalPropertyTypes: false 情况下,colorThemeOverride 则可以设置三个值:“dark”、“light”、“undefined”。
declare function getUserSettings(): UserDefaults;
// ---cut---
const settings = getUserSettings();
settings.colorThemeOverride = "dark";
settings.colorThemeOverride = "light";
settings.colorThemeOverride = undefined;
当我们设置 exactOptionalPropertyTypes: true 时,设置的如下代码就会报错:
settings.colorThemeOverride = undefined;
报错信息:
Type 'undefined' is not assignable to type '"dark" | "light"' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the type of the target.
解释:类型“undefined”不可分配给具有“exactOptionalPropertyTypes:true”的类型“dark”|“light”。请考虑将“undefined”添加到目标的类型中。
noFallthroughCasesInSwitch(No Fallthrough Cases In Switch)
noFallthroughCasesInSwitch 用于控制 TypeScript 编译器是否应该报错,当在 switch 语句中遇到一个 case 分支而没有使用 break、return、throw 或者下一个 case 分支的进入时(这通常被称为“case 穿透”或“case 跌落”)。
当 noFallthroughCasesInSwitch 设置为 true 时,如下示例:
const a: number = 6;
switch (a) {
case 0:
// 报错:switch 语句中的 Fallthrough 情况
console.log("even");
case 1:
console.log("odd");
break;
}
noImplicitAny(没有隐式any)
当设置 noImplicitAny: true
function fn(s) {
// 报错: Parameter 's' implicitly has an 'any' type.
console.log(s.subtr(3));
}
当设置noImplicitAny: false 就不会报上述错误。
noImplicitOverride(No Implicit Override)
noImplicitOverride是TypeScript 4.3版本引入的一个编译选项。这个选项的作用是当类中的方法被标记为override时,TypeScript 编译器会检查该方法是否确实在基类中有一个同名方法。如果没有,编译器会报错。
确保用重写修饰符标记派生类中的重写成员。
当设置noImplicitOverride: true时,如下示例:
class Album {
setup() {
// Default behavior
}
}
class MLAlbum extends Album {
setup() {
// 报错:此成员必须有 "override" 修饰符,因为它替代基类 "Album" 中的一个成员。
// Override to get info from algorithm
}
}
class SharedAlbum extends Album {
download() {
// Override to get info from many sources
}
}
如果要不报错,需要调整为:
class Album {
setup() {
// Default behavior
}
}
class MLAlbum extends Album {
override setup() {
// Override to get info from algorithm
}
}
class SharedAlbum extends Album {
download() {
// Override to get info from many sources
}
}
noImplicitReturns(No Implicit Returns)
启用后,TypeScript 将检查函数中的所有代码路径,以确保它们返回值。
function lookupHeadphonesManufacturer(color: "blue" | "black"): string {
// 报错:unction lacks ending return statement and return type does not include 'undefined'.
// 函数缺少结束 return 语句,返回类型不包括 "undefined"。
if (color === "blue") {
return "beats";
} else {
("bose");
}
}
noImplicitThis(No Implicit This)
启用后,在具有隐含“any”类型的“this”表达式上引发错误。也就是说当“this”的类型为“any”时启用错误报告。
如下示例,会报错:
class Rectangle {
width: number;
height: number;
constructor(width: number, height: number) {
this.width = width;
this.height = height;
}
getAreaFunction() {
return function () {
// 报错:'this' implicitly has type 'any' because it does not have a type annotation.
return this.width * this.height;
};
}
}
那我们可以优化上面代码:
class Rectangle {
width: number;
height: number;
constructor(width: number, height: number) {
this.width = width;
this.height = height;
}
getAreaFunction() {
// 使用箭头函数来保持对外部类实例的 'this' 的引用
return () => {
return this.width * this.height;
};
}
}
noPropertyAccessFromIndexSignature(不能直接从索引签名中访问属性)
当noPropertyAccessFromIndexSignature设置为true时,TypeScript编译器会限制你直接从索引签名中访问属性(限制通过dot语法访问属性,可以通过indexed语法访问属性)。这个选项主要在处理对象字面量和接口时起作用,特别是当这些对象或接口具有索引签名时。
默认没有开启,当设置了noPropertyAccessFromIndexSignature: true时,如下示例会报错。
示例如下:
interface MyObject {
[propName: string]: any;
}
const obj: MyObject = {
a: 1,
b: 2,
};
console.log(obj['a']); // 正确
console.log(obj.a); // 错误:属性“a”来自索引签名,因此必须使用[“a”]访问它
noUncheckedIndexedAccess(没有未经检查的索引访问)
开启后,当使用索引访问时,将“undefined”添加到类型中。
如下示例:
interface EnvironmentVars {
NAME: string;
OS: string;
// Unknown properties are covered by this index signature.
[propName: string]: string;
}
declare const env: EnvironmentVars;
// Declared as existing
const sysName = env.NAME;
const os = env.OS;
// Not declared, but because of the index
// signature, then it is considered a string
const nodeEnv = env.NODE_ENV;
上面代码中,os 跟 nodeEnv 属性检查提示的类型如下:
const os: string
const nodeEnv: string | undefined
noUnusedLocals(没有未使用的局部变量)
报告未使用的局部变量的错误。
当noUnusedLocals: true, 即开启后,如下示例会报错:
const createKeyboard = (modelID: number) => {
const defaultModelID = 23;
// 报错:'defaultModelID' is declared but its value is never read.
return { type: "keyboard", modelID };
};
noUnusedParameters(没有未使用的参数)
当设置为true时,报告函数中未使用的参数的错误。
const createDefaultKeyboard = (modelID: number) => {
// 'modelID' is declared but its value is never read.
const defaultModelID = 23;
return { type: "keyboard", modelID: defaultModelID };
};
strict(严格)
该标志支持广泛的类型检查行为,从而更强地保证程序的正确性。启用此功能等同于启用所有严格模式系列选项。然后,您可以根据需要关闭单个严格模式系列检查。
TypeScript 的未来版本可能会在此标志下引入额外的更严格的检查,因此 TypeScript 的升级可能会导致程序中出现新的类型错误。在适当且可能的情况下,将添加相应的标志以禁用该行为。
strictBindCallApply(Strict Bind Call Apply)
strictBindCallApply 主要是为了确保在使用 bind、call 和 apply 方法时,参数的类型是正确的。
如下示例:
function fn(x: string) {
return parseInt(x);
}
const n1 = fn.call(undefined, "10");
const n2 = fn.call(undefined, false); // 报错:类型“boolean”的参数不能赋给类型“string”的参数
注:strictBindCallApply 属于 strict 选项组的一部分。当你设置 strict 为 true 时,strictBindCallApply 也会默认启用,除非你明确地将它设置为 false。
strictFunctionTypes(严格的函数类型)
默认开启的。
启用后,此标志会更正确地检查函数参数。只有当一个函数的参数类型是另一个函数参数类型的子集时,才允许进行赋值。这有助于提高代码的类型安全性。
如下示例:
function fn(x: string) {
console.log("Hello, " + x.toLowerCase());
}
type StringOrNumberFunc = (ns: string | number) => void;
let func: StringOrNumberFunc = fn;
// 不能将类型“(x: string) => void”分配给类型“StringOrNumberFunc”。
// 参数“x”和“ns” 的类型不兼容。
// 不能将类型“string | number”分配给类型“string”。
// 不能将类型“string | number”分配给类型“string”。
在开发此功能的过程中,我们发现了大量本质上不安全的类层次结构,包括 DOM 中的一些类层次结构。 因此,该设置仅适用于以函数语法编写的函数,而不适用于以方法语法编写的函数:
也就是说,strictFunctionTypes 选项并不直接处理类或对象字面量的方法类型。它主要影响的是函数类型之间的兼容性。
type Methodish = {
func(x: string | number): void;
};
function fn(x: string) {
console.log("Hello, " + x.toLowerCase());
}
// Ultimately an unsafe assignment, but not detected
const m: Methodish = {
func: fn,
};
m.func(10);
上面代码就不会报错。
strictNullChecks(严格的 Null 检查)
如下示例,您在尝试使用它之前未保证它存在。
declare const loggedInUsername: string;
const users = [
{ name: "Oby", age: 12 },
{ name: "Heera", age: 32 },
];
const loggedInUser = users.find((u) => u.name === loggedInUsername);
console.log(loggedInUser.age); // 报错:“loggedInUser”可能为“未定义”。
示例二:
当 strictNullChecks: true 时
type Array = {
find(predicate: (value: any, index: number) => boolean): S | undefined;
};
上述示例中,find 方法的返回类型被定义为 S | undefined。这意味着调用 find 方法时,你必须考虑它可能返回 undefined 的情况,因为数组中的元素可能不满足给定的 predicate 函数。这增加了代码的健壮性,因为开发者必须显式地处理 undefined 的情况。
注:实际的 TypeScript 标准库中,Array.prototype.find 的定义已经考虑了 undefined 的可能性。无论 strictNullChecks 的值是什么,find 方法的返回类型都是 T | undefined
当 strictNullChecks: false 时
type Array = {
find(predicate: (value: any, index: number) => boolean): S;
};
上述代码示例中,find 方法的返回类型被简化为 S。当 strictNullChecks 被设置为 false 时,TypeScript 不会强制你考虑 null 或 undefined 的可能性。这可能导致你写出在运行时可能会出错的代码,因为你可能会假设 find 方法总是返回一个有效的 S 类型的值,而实际上它可能会返回 undefined。
strictPropertyInitialization(严格的属性初始化)
strictPropertyInitialization 默认开启。
设置为 true 时,当类属性已声明但未在构造函数中设置时,TypeScript 将引发错误。
示例:
class UserAccount {
name: string;
accountType = "user";
// 报错:roperty 'email' has no initializer and is not definitely assigned in the constructor.
// 属性“email”没有初始化表达式,且未在构造函数中明确赋值。
email: string;
address: string | undefined;
constructor(name: string) {
this.name = name;
// Note that this.email is not set
}
}
上述代码:
- this.name是专门设置的。
- this.accountType默认设置。
- this.email未设置并引发错误。
- this.address不会报错,因为已经允许设置为undefined
useUnknownInCatchVariables(在 Catch 变量中使用未知)
在 TypeScript 4.0 中,添加了支持,允许将 catch 子句中的变量类型声明为 unknown.
try {
// ...
} catch (err: unknown) {
// We have to verify err is an
// error before using it as one.
if (err instanceof Error) {
console.log(err.message);
}
}
此模式可确保错误处理代码变得更加全面,因为无法提前保证要抛出的对象是 Error 子类。
Modules(模块)
allowArbitraryExtensions(允许任意扩展)
默认值是 false,不开启的。
具体来说,当allowArbitraryExtensions设置为true时,TypeScript编译器将不再限制只导入具有已知文件扩展名(如.ts、.tsx、.js、.jsx等)的文件。这意味着你可以导入具有其他扩展名的文件,只要这些文件有对应的声明文件,TypeScript编译器就能理解这些文件的结构和类型。
allowImportingTsExtensions(允许导入 TS 扩展)
允许导入包含TypeScript文件扩展名,如.ts.mts.tsx。要求设置“--moduleSolution bundler”和“--noEmit”或“--emitDeclarationOnly”
allowUmdGlobalAccess(允许 Umd 全局访问)
allowUmdGlobalAccess是一个选项,当设置为true时,它允许在TypeScript模块中通过全局变量的方式访问UMD(Universal Module Definition)模块。
UMD模块定义是一个兼容AMD(Asynchronous Module Definition)和CommonJS的模块定义,它能在多种模块加载器(如RequireJS、AMD、Node.js等)下工作。
allowUmdGlobalAccess的作用就是允许你在TypeScript模块中直接访问UMD模块暴露的全局变量。这在你需要访问某些全局库或框架(如jQuery、Underscore等)时非常有用,因为这些库或框架可能会以UMD模块的形式发布,并在全局作用域下暴露一些变量或函数。
baseUrl
目录结构:
project
├── ex.ts
├── hello
│ └── world.ts
└── tsconfig.json
tsconfig.json配置:
"baseUrl": "./"
导入模块:
import { helloWorld } from "hello/world";
console.log(helloWorld);
此解析的优先级高于从node_modules中查找的优先级。
从 TypeScript 4.1 开始,使用路径时不再需要设置。
customConditions
customConditions 是 tsconfig.json 中的一个选项,它允许你为 TypeScript 的模块解析添加额外的条件。这在你想要自定义模块导入或导出时非常有用,特别是当你想要根据特定的条件(这些条件可能不是 TypeScript 或 Node.js 默认支持的)来加载不同的模块时。
作用:
- 当 TypeScript 解析 package.json 中的 exports 或 imports 字段时,它会考虑 customConditions 中列出的额外条件。
- 这些条件会被添加到解析器默认使用的任何现有条件中。
用法:
- 在 tsconfig.json 文件的 compilerOptions 中设置 customConditions 选项,并提供一个字符串数组,其中包含你想要添加的条件名称。
- 当你从具有 exports 或 imports 字段的 package.json 导入模块时,TypeScript 会考虑这些额外的条件。
假设你有以下的 tsconfig.json:
{
"compilerOptions": {
"target": "es2022",
"moduleResolution": "bundler",
"customConditions": ["my-condition"]
}
}
package.json:
{
// ...
"exports": {
".": {
"my-condition": "./foo.mjs",
"node": "./bar.mjs",
"import": "./baz.mjs",
"require": "./biz.mjs"
}
}
}
当你从该包导入模块时(例如,使用 import something from 'package-name';),如果 TypeScript 的模块解析器正在使用 bundler 模式(或 node16、nodenext),它会尝试查找与 my-condition 条件对应的文件(即 foo.mjs)。
注意事项:
- customConditions 仅在 moduleResolution 设置为 node16、nodenext 或 bundler 时有效。
- 确保你的构建工具或运行环境支持你定义的自定义条件。例如,Node.js 本身可能不支持 customConditions,但你可以使用支持它的 TypeScript 插件或构建工具。
- 当 TypeScript 解析模块时,它会考虑所有列出的条件,包括默认的(如 node、import、require)和你定义的自定义条件。如果多个条件匹配,行为可能会因解析器而异,但通常它会选择第一个匹配的入口点。
module(模块)
生成代码使用的模块化(指定生成哪个模块系统代码):"None", "CommonJS", "AMD", "System", "UMD", "ES6"或 "ES2015"等等。
默认值:
target === "ES6" ? "ES6" : "commonjs"
示例:
// @filename: index.ts
import { valueOfPi } from "./constants";
export const twoPi = valueOfPi * 2;
CommonJS:
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.twoPi = void 0;
const constants_1 = require("./constants");
exports.twoPi = constants_1.valueOfPi * 2;
UMD:
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports", "./constants"], factory);
}
})(function (require, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.twoPi = void 0;
const constants_1 = require("./constants");
exports.twoPi = constants_1.valueOfPi * 2;
});
moduleResolution
用于指定TypeScript编译器在解析模块时所使用的策略。默认值:
module === "AMD" or "System" or "ES6" ? "Classic" : "Node"
作用:
- 模块解析策略:moduleResolution定义了编译器如何查找模块的定义文件,如.ts、.tsx或.d.ts文件。
- 两种策略:TypeScript提供了两种模块解析策略:Node和Classic。
1.node:
- 当使用moduleResolution设置为Node时,编译器会模拟Node.js的模块解析机制来查找模块。
- 它会首先在当前目录下查找node_modules文件夹中的模块。
- 如果没有找到,它会继续向上查找父目录中的node_modules文件夹,直到根目录或找到为止。
- 这种策略与Node.js的模块解析方式一致,因此适用于Node.js环境下的TypeScript项目。
当使用node的值有如下几种:
- node16:Node.js v12 及更高版本支持 ECMAScript 导入和 CommonJS
- node10:用于早于 v10 的Node.js版本,仅支持 CommonJS 。
2.Classic:
- 当使用moduleResolution设置为Classic时,编译器会按照TypeScript的传统方式来解析模块。
- 它主要依赖于相对路径和非相对路径来查找模块。
- 相对路径是以/、./或../开头的,它们会相对于当前文件来解析模块。
- 非相对路径则会被视为相对于某些根目录或特定的路径来解析,但具体规则可能因项目而异。
moduleSuffixes(模块后缀)
提供一种在解析模块时覆盖要搜索的文件名后缀的默认列表的方法。
示例:
{
"compilerOptions": {
"moduleSuffixes": [".ios", ".native", ""]
}
}
给定上述配置,导入如下所示:
import * as foo from "./foo";
TypeScript 将查找相对文件,最后是 ./foo.ios.ts, ./foo.native.ts, ./foo.ts。
请注意 moduleSuffixes 中的空字符串,这是 TypeScript 查找所必需的。./foo.ts
noResolve
默认情况下,TypeScript 会检查初始文件集中的 import 和
如果设置了 noResolve,这个过程就不会发生。但是,import 语句仍然会被检查以确认它们是否能解析到一个有效的模块,因此你需要通过其他方式确保这一点得到满足。
简而言之,noResolve 配置项用于阻止 TypeScript 自动解析并包含项目中引用的外部模块。但是,TypeScript 编译器仍然会检查 import 语句,以确保它们指向了有效的模块路径。如果 noResolve 被启用,你需要自己处理这些模块的解析和包含工作,可能是通过其他构建工具或配置来完成。
paths
paths 的主要作用包括:
- 别名解析:你可以为模块或库定义别名,这样你就可以在 TypeScript 代码中使用这些别名来引用它们,而不是使用相对或绝对路径。
- 代码清晰性:使用别名可以使你的代码更加清晰和易于理解。例如,你可以使用 @myLib/someModule 而不是 ../../../../../src/libs/myLib/someModule。
- 重定向:你可以将别名重定向到不同的模块或文件。这对于某些构建工具(如 Webpack)来说特别有用,因为它们可以利用这些别名来执行更复杂的操作,如代码拆分、懒加载等。
- 跨项目引用:在大型项目中,你可能需要在多个包或项目之间共享代码。使用别名可以使这些引用更加简单和一致。
示例:
{
"compilerOptions": {
// ... 其他选项 ...
"baseUrl": ".", // 这通常是 tsconfig.json 文件所在的目录
"paths": {
"@myLib/*": ["src/libs/myLib/*"],
"@components/*": ["src/components/*"],
"lodash": ["node_modules/lodash-es/lodash.js"] // 这是一个示例,通常不建议这样做,因为它会覆盖 node_modules 中的 lodash
}
},
// ... 其他选项 ...
}
paths 配置只是告诉 TypeScript 编译器如何解析模块路径。如果你希望这些别名在运行时也有效(例如,在浏览器中或 Node.js 环境中),你可能还需要配置其他工具(如 Webpack、ts-node 等)来支持这些别名。
总上所述,如果要在ts中通过别名引用文件,需要配置paths。同时想工程运行时有效,也需要在webpack类似工具中配置这些别名来支持,如下示例:
webpack:
resolve: {
alias: {
'@utils': path.resolve(__dirname, "../src/utils/"),
}
}
tsconfig.json配置:
"paths": {
"@utils/*": ["./src/utils/*"],
}
ts页面使用别名:
import { version, doSomething } from '@utils/module-lib';
console.log(version);
doSomething();
resolveJsonModule(解析 JSON 模块)
TypeScript 默认不支持解析 JSON 文件.
如下示例代码:
// @filename: settings.json
{
"repo": "TypeScript",
"dry": false,
"debug": false
}
// @filename: index.ts
import settings from "./settings.json";
在index.ts代码中引入settings.json会报错,提示:Cannot find module './settings.json'. Consider using '--resolveJsonModule' to import module with '.json' extension.
我们在tsconfig.json中配置如下:
"compilerOptions": {
resolveJsonModule: true
}
启用该选项允许导入 JSON,就能正常的导入JSON文件了。
resolvePackageJsonExports
resolvePackageJsonExports强制TypeScript在读取node_modules中的包时查阅package.json文件的exports字段。
当moduleResolution设置为Node16,NodeNext,Bundler时, 该选项的默认值自动设置为true。
应用场景示例:
假设你有一个库,其目录结构如下,并且希望根据导入方是Node.js环境还是浏览器环境来提供不同版本的导出:
my-library/
├── dist/
│ ├── browser/
│ │ └── index.js
│ └── node/
│ └── index.js
├── package.json
└── tsconfig.json
在 package.json 中,你可以定义 "exports" 来指导模块解析:
{
"name": "my-library",
"version": "1.0.0",
"exports": {
".": {
"require": "./dist/node/index.js",
"import": "./dist/browser/index.js"
}
}
}
在使用此库的 TypeScript 项目中,如果启用了 resolvePackageJsonExports,TypeScript 编译器将能够正确解析这些条件导出,并给予正确的导入提示和类型检查。
resolvePackageJsonImports
当从一个文件导入以#开头的value或者type时, 如果这个文件所在的目录(或者祖先目录)内有一个package.json文件, 开启这个选项会强制 ts 从package.json imports字段确认导入的实际文件。
"imports" 字段允许库作者指定导入别名或者重定向导入路径,使得用户可以以更简洁或更符预期的方式导入模块。
当moduleResolution设置为Node16,NodeNext,Bundler时, 该选项的默认值自动设置为true。
应用场景示例:
考虑一个库有深层嵌套的模块结构,但希望用户提供一个简洁的导入路径。在 package.json 中可以这样定义:
{
"name": "my-library",
"version": "1.0.0",
"imports": {
"#utils": "./src/utils/index.ts"
}
}
这样,用户就可以直接这样导入:
import { myUtil } from 'my-library/#utils';
而不必关心实际的文件路径。启用 resolvePackageJsonImports后,TypeScript编译器能够识别并正确解析这些通过 "imports" 定义的别名。
rootDir
compilerOptions.rootDir 属性用于指定项目的根目录,即 TypeScript 编译器在寻找输入文件时会从这个目录开始查找。这个设置对于组织大型项目、控制编译输出结构以及确保只编译项目源代码而非其他如测试文件或构建产物非常有用。
主要作用:
- 控制输入范围: 指定哪些源代码文件应该被 TypeScript 编译器考虑进行编译。
- 避免不必要的文件编译:通过限制编译器搜索的范围,可以排除测试文件、配置文件等不需要编译的文件。
- 隔离源代码与输出: 结合 outDir 使用时,可以将源代码与编译后的 JavaScript 文件分开存放,保持项目目录清晰。
场景示例:
例如,我们的项目结构:
my-project/
├── src/
│ ├── index.ts
│ ├── utils/
│ └── helper.ts
├── tests/
│ └── index.test.ts
├── tsconfig.json
└── package.json
希望只有 src 目录下的 TypeScript 文件被编译,而测试文件和配置文件等不被编译。这时可以在 tsconfig.json 中这样配置:
"compilerOptions": {
"rootDir": "src" // 指定源代码根目录为 "src"
},
一般情况,我们设置rootDir为src目录,如果工程中国际化并且使用的是kiwi实现的国际化,那么在根目录下存在.kiwi文件夹,里面存放的是ts文件也是需要解析的。因此这种情况我们可以使用rootDir默认配置,即,rootDir: "./"
rootDirs
利用rootDirs指定虚拟目录。
有时多个目录下的工程源文件在编译时会进行合并放在某个输出目录下。这可以看做一些源目录创建了一个“虚拟”目录。
利用rootDirs,可以告诉编译器生成这个虚拟目录的roots; 因此编译器可以在“虚拟”目录下解析相对模块导入,就 好像它们被合并在了一起一样。
比如,有下面的工程结构:
src
└── views
└── view1.ts (imports './template1')
└── view2.ts
generated
└── templates
└── views
└── template1.ts (imports './view2')
src/views里的文件是用于控制UI的用户代码。 generated/templates是UI模版,在构建时通过模版生成器自动生成。 构建中的一步会将/src/views和/generated/templates/views的输出拷贝到同一个目录下。在运行时,视图可以假设它的模版与它同在一个目录下,因此可以使用相对导入 "./template"。
可以使用"rootDirs"来告诉编译器。"rootDirs"指定了一个roots列表,列表里的内容会在运行时被合并。 因此,针对这个例子, tsconfig.json如下:
{
"compilerOptions": {
"rootDirs": [
"src/views",
"generated/templates/views"
]
}
}
每当编译器在某一rootDirs的子目录下发现了相对模块导入,它就会尝试从每一个rootDirs中导入。
主要作用:
- 多目录合并: 使得编译器能够跨多个物理目录寻找模块,而对使用者来说就像是一个目录。
- 兼容遗留系统: 在需要维护代码结构或组织的同时,保持导入路径的一致性,特别适用于大型项目重构或有特定部署需求的情况。
- 分隔源代码与共享代码:可以将共享代码放在单独的目录,同时不影响导入路径,使得项目结构更加清晰。
场景示例:
目录结构如下:
src
└── index.ts
share-utils
└── share-utils.ts
在src同级目录新建如下文件share-utils/share-utils.ts,示例代码如下:
export function sum(a: number,b: number): number {
return a + b
}
tsconfig.json配置如下:
{
"compilerOptions": {
"rootDirs": [
"src",
"share-utils"
],
}
}
在src/index.ts如下配置引入:
import { sum } from './share-utils';
console.log(sum(1,2))
上述示例,在 TypeScript 编译时非常有用,但是项目运行时,上述代码是报错的,因为运行时的解析路径是在src路径下,所以是找不到该文件的,需要结合webpack等打包工具设置如 resolve.alias 选项来创建自定义的模块解析别名。
所以针对上述情况,我们一般结合tsconfig.json文件中的paths配置以及webpack配置来实现路径别名的解析,而不用使用相对路径或者绝对路径是引用相应的资源文件。
最终如上代码配置调整为如下。
tsconfig.json配置如下:
{
"compilerOptions": {
"paths": {
"@shared/*": ["share-utils/*"]
},
"rootDirs": [
"src",
"share-utils"
],
}
}
webpack 配置如下:
resolve: {
alias: {
"@shared": path.resolve(__dirname, "../share-utils/"),
},
},
src/index.ts引用调整为:
import { sum } from '@shared/share-utils';
typeRoots(类型声明查找目录)
tsconfig.json 文件中的 typeRoots 配置项用于指定 TypeScript 编译器在解析类型定义文件(.d.ts 文件)时应该查找的目录。这些类型定义文件通常用于为 JavaScript 库提供类型信息,以便在 TypeScript 项目中更好地使用它们。
默认情况下,TypeScript 编译器会包含所有可见的 "@types" 包。这里的“可见”意味着它会查找项目根目录及其所有父级目录下的 node_modules/@types 文件夹。
当你指定了 typeRoots 配置项时,TypeScript 编译器的行为将有所变化。它将只会包含 typeRoots 中指定的目录下的类型定义文件,而不再查找默认的 node_modules/@types 目录。
示例,假设你有如下的 tsconfig.json 配置:
{
"compilerOptions": {
"typeRoots": ["./typings", "./vendor/types"]
}
}
在这个配置中,TypeScript 编译器将只会查找 ./typings 和 ./vendor/types 这两个目录下的类型定义文件。它不会再去查找默认的 node_modules/@types 目录。注意,这里的路径是相对于 tsconfig.json 文件所在的目录的。
如果还需要在node_modules/@types目录下查找,那么我们tsconfig.json 一般这样配置:
{
"compilerOptions": {
"typeRoots": ["./typings", "./vendor/types","node_modules/@types"]
}
}
注意:即使不显式设置 typeRoots,TypeScript 编译器也能够自动查找项目中的 .d.ts 类型声明文件。
types
默认情况下,所有可见的 @types 包都会被包含在你的编译中。这些可见的包包括任何父级目录下的 node_modules/@types 文件夹中的包。例如,这意味着在./node_modules/@types/、../node_modules/@types/、../../node_modules/@types/ 等目录下的包都是可见的。
如果你指定了 types 字段,那么只有在这个列表中列出的包才会被包含在全局作用域中。例如:
{
"compilerOptions": {
"types": ["node", "jest", "express"]
}
}
这个 tsconfig.json 文件只会包含./node_modules/@types/node、./node_modules/@types/jest 和 ./node_modules/@types/express 这三个包。node_modules/@types/*下的其他包将不会被包含。
这个配置对什么有影响?
这个选项不会影响 @types/* 包如何在你的应用代码中被包含。例如,如果你有如下的 compilerOptions 配置和代码:
import * as moment from "moment";
moment().format("MMMM Do YYYY, h:mm:ss a");
即使你没有在 types 字段中列出 moment,moment 的导入也会是完全类型化的,因为 TypeScript 会查找 node_modules/moment 下的类型定义(如果存在的话)。
从npm官网查看moment下载的目录,该目录下确实是有对该插件进行了类型声明:
dist/
locale/
min/
src/
ts3.1-typings/
CHANGELOG.md
LICENSE
README.md
ender.js
moment.d.ts
moment.js
package.js
package.json
与 typeRoots 的区别
types 和 typeRoots 这两个配置选项的主要区别在于它们的关注点不同:
- types 是关于指定你确切想要包含的类型包,这些包会被添加到全局作用域中。
- typeRoots 则是关于指定你希望 TypeScript 编译器搜索类型定义文件的目录。你可以使用 typeRoots 来覆盖默认的 node_modules/@types 目录,并指定其他目录来搜索类型定义文件。
Emit
declaration
declaration 是 tsconfig.json 中的一个配置项,用于指示 TypeScript 编译器是否为项目中的每个 TypeScript 或 JavaScript 文件生成相应的声明文件(.d.ts 文件)。这些声明文件描述了模块的外部 API,并包含类型定义,这样其他 TypeScript 文件或支持 TypeScript 类型的工具(如编辑器中的智能感知功能)就能理解并使用这些模块的类型。
当 declaration 设置为 true 时:
- TypeScript 编译器会为项目中的每个 .ts 文件生成一个对应的 .d.ts 声明文件。
例如有如下helloworld.ts文件,其内容为:
export let helloWorld = "hi";
对于上述代码,编译器会生成一个名为helloworld.d.ts的文件,内容如下:
export declare let helloWorld: string;
注意,declaration 选项不会生成 JavaScript 文件(.js 文件)。它仅生成类型声明文件。
注意事项
与 JavaScript 文件一起使用时:
- 你可以使用 emitDeclarationOnly 选项与 declaration 一起,这样编译器只会生成声明文件,而不会生成 JavaScript 文件。这在你仅需要类型声明,而不需要实际的 JavaScript 输出时很有用。
- 使用 outDir 选项可以指定生成的声明文件和(如果启用)JavaScript 文件的输出目录。这有助于避免与源代码文件混淆,并确保声明文件不会覆盖你的 JavaScript 文件。
示例,tsconfig.json配置:
{
"compilerOptions": {
"declaration": true,
"outDir": "./dist"
}
}
我们执行tsc命令后,会在dist文件夹生成对应目录的.d.ts文件。如果我们不指定outDir目录,默认生成的.d.ts文件就是ts源文件同目录下。
declarationDir(声明文件输出目录)
示例:
example
├── index.ts
├── package.json
└── tsconfig.json
tsconfig.json配置:
{
"compilerOptions": {
"declaration": true,
"declarationDir": "./types"
}
}
将d.ts放在文件夹types中:
example
├── index.js
├── index.ts
├── package.json
├── tsconfig.json
└── types
└── index.d.ts
emitDeclarationOnly(只生成声明文件)
默认情况下,我们对ts文件既生成声明文件.d.ts,也生成对应的js文件。
当我们在默认情况下,对tsconfig.json进行了如下配置:
{
"compilerOptions": {
"declaration": true,
"outDir": "./dist"
"declarationDir": "./types"
}
}
那么生成的JS文件会被输出到dist下面,.d.ts文件在types文件夹下。
当我们设置emitDeclarationOnly: true时,编译器编译的时候,不会生成对应的js文件。
注:当开启emitDeclarationOnly: true时,解析入口文件index.ts报错。
declarationMap
用于指示 TypeScript 编译器是否为生成的 .d.ts 声明文件生成源映射(source map)。
源映射(source map)是一个数据结构,它提供了从编译后的代码(在这个情况下是 TypeScript 编译后的 .d.ts 文件)到原始源代码(即 .ts 文件)的映射信息。
downlevelIteration(降级迭代)
在 tsconfig.json 文件中,downlevelIteration 是一个编译选项,用于控制 TypeScript 编译器如何将使用现代 JavaScript 迭代特性(如 for...of 循环)的代码编译到较旧的 JavaScript 版本(特别是 ES5)。
在 ECMAScript 6(ES6)及更高版本中,JavaScript 引入了新的迭代原语,如 for...of 循环、数组展开(spread)[...arr]、函数参数展开 fn(...args),以及 Symbol.iterator
如下TS代码:
const str = "Hello!";
for (const s of str) {
console.log(s);
}
当我们没有启用downlevelIteration的时候,如果"target":"es5"时,那么编译后的js代码如下:
"use strict";
var str = "Hello!";
for (var _i = 0, str_1 = str; _i < str_1.length; _i++) {
var s = str_1[_i];
console.log(s);
}
当我们启用downlevelIteration的时候,即设置它的值为true时,tsconfig.json配置如下:
{
"compilerOptions": {
"target": "es5",
"downlevelIteration": true
}
}
编译后的JS代码如下:
"use strict";
var __values = (this && this.__values) || function(o) {
var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
if (m) return m.call(o);
if (o && typeof o.length === "number") return {
next: function () {
if (o && i >= o.length) o = void 0;
return { value: o && o[i++], done: !o };
}
};
throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
};
var e_1, _a;
var str = "Hello!";
try {
for (var str_1 = __values(str), str_1_1 = str_1.next(); !str_1_1.done; str_1_1 = str_1.next()) {
var s = str_1_1.value;
console.log(s);
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (str_1_1 && !str_1_1.done && (_a = str_1.return)) _a.call(str_1);
}
finally { if (e_1) throw e_1.error; }
}
从上面代码可以看出,target 设置为 "es5" 表明你希望编译出的代码兼容 ES5 环境,而 "downlevelIteration": true 则确保了迭代功能的兼容性。
注意: 当目标输出为 ES6 或更高版本 ("target": "es2015" 及以上),通常不需要设置 downlevelIteration,因为现代环境已经原生支持迭代器和生成器。
当我们对tsconfig.json进入如下配置:
{
"compilerOptions": {
"target": "es2016",
"downlevelIteration": true
}
}
上面的ts代码,编译为js的代码如下:
"use strict";
const str = "Hello!";
for (const s of str) {
console.log(s);
}
可以看出,如果设置了target为高版本的ES,那么编译出来的JS基本是不变的,只是在头部加了一个严格模式的代码。
emitBOM
官网建议默认关闭。
emitBOM 是 TypeScript 编译器选项中的一个配置项,用于控制是否在输出的文件前添加 Unicode 字节顺序标记(Byte Order Mark,简称 BOM)。BOM 是一个特殊字符,放置在文本文件的最开始,用来标识文件的编码格式,尤其是对于 UTF-8 编码非常有用。它并不改变文件的实际内容,但可以帮助某些文本编辑器或处理程序正确识别文件的编码。
作用
- 编码指示:BOM 可以帮助编辑器或解析器自动检测文件使用的是 UTF-8 编码,尤其是在没有其他元数据指示编码的情况下。
- 兼容性问题:虽然 BOM 对于某些场景是有益的,但它也可能导致问题,特别是在一些旧的软件或特定格式的文件处理中,BOM 可能会被错误地显示或处理。
tsconfig.json配置如下:
{
"compilerOptions": {
"emitBOM": true
}
}
当 TypeScript 编译器生成 .js 文件时,它会在每个文件的开头添加一个 BOM。
注意事项
- 大多数现代编辑器和 Web 浏览器都能很好地处理带有 BOM 的 UTF-8 文件,但不是所有工具都支持或需要它。例如,在某些Web服务器上,BOM可能会导致HTTP响应头被误解,或者在通过XMLHttpRequest加载脚本时出现问题。
- 当你的项目需要与其他不期望或不支持 BOM 的系统集成时,应谨慎使用此选项,并可能需要将其设置为 false。
- 实际编写代码示例对于 emitBOM 配置项并不适用,因为这是编译时的一个设置,不影响代码本身的逻辑或结构。它更多是关于输出文件的格式处理。
注:BOM 是一个不可见的特殊字符,对于 UTF-8 编码的文件,BOM 是 EF BB BF 这三个字节。
具体实现对比,当我们默认不开启emitBOM配置项的时候,使用vscode底部栏可以查看当前文件的编码格式:
UTF-8
当我们开启emitBOM配置项的时候,用vscode查看当前编译的文件编码格式:
UTF-8 with BOM
importHelpers(导入助手)
对于某些降级操作,TypeScript使用一些辅助代码进行操作,如扩展类、扩展数组或对象以及异步操作。默认情况下,这些辅助对象会插入到使用它们的文件中。如果在许多不同的模块中使用相同的帮助程序,这可能会导致代码重复。
如果该配置项importHelpers处于启用状态,则这些辅助函数将改为从tslib模块导入。
这儿的辅助函数,可以理解成降级后需要用到的一些兼容的通用方法。
如下TypeScript代码:
export function fn(arr: number[]) {
const arr2 = [1, ...arr];
}
tsconfig.json配置文件,默认不开启importHelpers:
{
"compilerOptions": {
"target": "es5",
"resolveJsonModule": true
}
}
上面ts代码编译后:
"use strict";
var __read = (this && this.__read) || function (o, n) {
var m = typeof Symbol === "function" && o[Symbol.iterator];
if (!m) return o;
var i = m.call(o), r, ar = [], e;
try {
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
}
catch (error) { e = { error: error }; }
finally {
try {
if (r && !r.done && (m = i["return"])) m.call(i);
}
finally { if (e) throw e.error; }
}
return ar;
};
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
if (ar || !(i in from)) {
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
ar[i] = from[i];
}
}
return to.concat(ar || Array.prototype.slice.call(from));
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.fn = void 0;
function fn(arr) {
var arr2 = __spreadArray([1], __read(arr), false);
}
exports.fn = fn;
当我们调整tsconfig.json代码,开启importHelpers:
{
"compilerOptions": {
"target": "es5",
"resolveJsonModule": true,
"importHelpers": true
}
}
编译后的JS代码:
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.fn = void 0;
var tslib_1 = require("tslib");
function fn(arr) {
var arr2 = tslib_1.__spreadArray([1], tslib_1.__read(arr), false);
}
exports.fn = fn;
从上面的代码可以看出,当在 TypeScript 项目中启用 importHelpers 时,TypeScript 编译器会将一些辅助函数(helpers)从 tslib 模块中导入,而不是在每个使用这些辅助函数的文件中重复生成这些代码。这可以减少最终生成的 JavaScript 代码的体积,并提高代码的可读性和可维护性。
importsNotUsedAsValues
已弃用,在5.5版本之后将停止运行。
importsNotUsedAsValues这个配置项曾经是用于控制当导入(import)仅被用作类型而不是值时,TypeScript 应该如何处理这些导入的。然而,根据你所提供的说明,这个选项已经被 verbatimModuleSyntax 所取代(尽管在某些版本的 TypeScript中可能仍然可用,但已经不再推荐)。
这个配置项有三个可能的值:
- 1.remove(默认)
当导入仅被用作类型(例如,使用 import type 语法)时,TypeScript 不会在生成的 JavaScript 文件中包含这些导入语句。这有助于减少输出文件的大小,因为运行时并不需要这些类型信息。
示例:
// TypeScript
import type { MyType } from './myModule';
function doSomething(arg: MyType) { /* ... */ }
// 生成的 JavaScript(如果 importsNotUsedAsValues 设置为 "remove")
function doSomething(arg) { /* ... */ }
- 2.preserve
无论导入是否被用作值或类型,TypeScript 都会保留这些导入语句。这可能导致一些仅用于产生副作用(side-effects)的导入也被保留下来。
示例:
// TypeScript
import './myModuleWithSideEffects'; // 仅用于产生副作用
import type { MyType } from './myModule';
function doSomething(arg: MyType) { /* ... */ }
// 生成的 JavaScript(如果 importsNotUsedAsValues 设置为 "preserve")
import './myModuleWithSideEffects'; // 保留副作用导入
function doSomething(arg) { /* ... */ }
- 3.error
这个选项与 preserve 类似,都会保留所有导入。但是,当某个值导入仅被用作类型时,TypeScript 会抛出一个错误。这在你想要确保没有值被意外导入,但同时又希望明确保留有副作用的导入时可能很有用。
示例(注意:实际上这个选项可能不会直接产生错误,除非 TypeScript 的实现有所变化或特定于某些用法):
示例:
// TypeScript
import { MyType } from './myModule'; // 注意这里不是 import type
function doSomething(arg: MyType) { /* ... */ }
// 如果 importsNotUsedAsValues 设置为 "error",可能会期望一个编译错误,
// 因为 MyType 仅被用作类型,但它是一个值导入。
// 但实际上,TypeScript 通常会允许这样的用法,除非有额外的检查或规则。
注意:
- import type:从 TypeScript 3.8 开始,你可以使用 import type语句来明确告诉TypeScript 某个导入仅用于类型,这样即使 importsNotUsedAsValues设置为remove,你仍然可以在 TypeScript文件中看到这个导入,但在生成的 JavaScript 文件中它会被省略。
- verbatimModuleSyntax:这个选项取代了 importsNotUsedAsValues,用于控制模块导入的详细语法。然而,具体的行为和用途可能因 TypeScript 的版本和配置而异。
inlineSourceMap
- 写入 source map 的方式:默认情况下,当启用 source map(通过 sourceMap 配置项设置为 true)时,TypeScript 会生成一个 .js.map 文件,该文件包含了将生成的 JavaScript 代码映射回原始 TypeScript 代码的信息。这允许开发者在浏览器中调试 TypeScript 代码,即使浏览器实际执行的是 JavaScript。
- inlineSourceMap 的作用:当 inlineSourceMap 设置为 true时,TypeScript将不会生成一个单独的 .js.map 文件,而是将 source map 的内容以Base64 编码的形式嵌入到生成的JavaScript文件的末尾。这通过在文件的末尾添加一个特殊的注释(如//#sourceMappingURL=data:application/json;base64,...)来实现,其中... 是 Base64 编码的 source map 内容。
- 文件大小的影响:由于 source map 通常包含大量信息,将其嵌入到 JavaScript 文件中会导致生成的 JS 文件变大。这可能会增加文件传输时间,但在某些场景下(如上述提到的 web 服务器限制),这可能需要有一个权衡。
- 与 sourceMap 的互斥性:inlineSourceMap 和 sourceMap 是互斥的,即不能同时设置为 true。如果你同时启用了这两个选项,TypeScript 将会忽略 sourceMap 的设置,并只使用 inlineSourceMap。
示例,tsconfig.json配置:
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"inlineSourceMap": true, // 启用内联 source map
"strict": true
}
}
如下示例, TypeScript:
const helloWorld = "hi";
console.log(helloWorld);
生成的 JavaScript 文件:
"use strict";
const helloWorld = "hi";
console.log(helloWorld);
//# sourceMappingURL=data:application/json;base64,ey......
source map 的内容被 Base64 编码并嵌入到了 JavaScript 文件的末尾。这样,即使在没有 .js.map 文件的情况下,浏览器也可以解码这个注释中的 source map 信息,以支持 TypeScript 代码的调试。
我们解密上面的base64可以看到是如下内容:
{
"version":3,
"file":"hello.js",
"sourceRoot":"",
"sources":["hello.ts"],
"names":[],
"mappings":";AAAA,IAAM,UAAU,GAAG,IAAI,CAAC;AACxB,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC"
}
inlineSources
- 当 inlineSources 被设置时,TypeScript 会在 source map 中包含一个嵌入式字符串,该字符串是文件的原始内容(使用 source map 的 sourcesContent 属性)。
- 在需要直接查看原始源代码而不需要额外文件的情况下很有用,类似于 inlineSourceMap 的用途。
- 需要注意的是,inlineSources 只有在 sourceMap 或 inlineSourceMap 也被设置的情况下才有效。
如下示例, TypeScript:
const helloWorld = "hi";
console.log(helloWorld);
默认情况下,转换为以下 JavaScript:
"use strict";
const helloWorld = "hi";
console.log(helloWorld);
当我们设置了inlineSources:true时,使用 TypeScript 编译器编译这个文件时,它会生成一个 JavaScript 文件(例如 index.js)和一个内联的 source map。这个 source map 会被附加到 JavaScript 文件的末尾,并且会包含原始 TypeScript 文件的源代码。
注:设置inlineSources之前,要先开启sourceMap选项或者inlineSourceMap选项才能生效。
生成的 JavaScript 文件:
"use strict";
const helloWorld = "hi";
console.log(helloWorld);
//# sourceMappingURL=data:application/json;base64,ey......
这里 source map 已经被 base64 编码并简化为展示目的,上面的示例中的 ... 替代了实际的 base64 编码内容。
解析这个base64内容为如下:
{
"version":3,
"file":"hello.js",
"sourceRoot":"",
"sources":["hello.ts"],
"names":[],
"mappings":";AAAA,IAAM,UAAU,GAAG,IAAI,CAAC;AACxB,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC",
"sourcesContent":["const helloWorld = \"hi\";\r\nconsole.log(helloWorld);"]
}
可以看到设置这个inlineSources后,在sourceMappingURL有个sourcesContent属性,里面存放的就是源码。
注意:在某些情况下,如果你的构建系统或打包器(如 Webpack)已经处理了 source map 和源代码的嵌入,你可能不需要在 tsconfig.json 中设置 inlineSources。
mapRoot
mapRoot 配置项是用于指定调试器在查找 TypeScript 生成的源映射文件(.map 文件)时应该使用的根路径。当 TypeScript 编译器生成 JavaScript 文件和对应的源映射文件时,mapRoot 允许你指定一个不同的路径,该路径将用于在源映射文件中表示源映射文件的位置。
在开发过程中,TypeScript 编译器通常会将 TypeScript 代码编译为 JavaScript,并生成源映射文件以支持调试。源映射文件包含了将编译后的 JavaScript 代码映射回原始 TypeScript 源代码的信息,使得开发者可以在浏览器中直接调试 TypeScript 代码。
默认情况下,源映射文件会与编译后的 JavaScript 文件位于同一目录下,但有时候你可能希望将源映射文件存储在不同的位置,或者通过 CDN 来提供源映射文件,以提高访问速度。mapRoot 就是为这种情况设计的。
在 tsconfig.json 中,如果你没有设置 mapRoot,那么生成的源映射文件 index.js.map 将与 index.js 位于同一目录下(即 /dist 目录下)。
但是,如果你设置了 mapRoot 为 "https://my-website.com/debug/sourcemaps/",那么生成的 index.js 文件中的源映射引用将不再是相对路径,而是指向你指定的 mapRoot 路径。
示例:
tsconfig.json配置文件:
{
"compilerOptions": {
"sourceMap": true,
"mapRoot": "https://my-website.com/debug/sourcemaps/"
}
}
typescript代码:
const helloWorld = "hi";
console.log(helloWorld);
编译后的js代码:
"use strict";
var helloWorld = "hi";
console.log(helloWorld);
//# sourceMappingURL=https://my-website.com/debug/sourcemaps/share-utils/hello.js.map
可以看到sourceMappingURL的路径就包含了刚才mapRoot配置的路径。
newLine
在 tsconfig.json 文件中,newLine 配置项用于指定在输出文件(例如编译后的 .js 文件)时所使用的行尾序列。这对于跨平台开发尤其重要,因为不同的操作系统使用不同的行尾字符来表示文本文件的换行。
- 'CRLF'(Carriage Return Line Feed):这是 Windows 和 DOS 系统使用的行尾字符序列。每个新行都以回车符(\r)后跟换行符(\n)来表示。
- 'LF'(Line Feed):这是 Unix 和 Unix-like 系统(如 Linux 和 macOS)使用的行尾字符。每个新行都以换行符(\n)来表示。
示例:
假设你有一个跨平台的 TypeScript项目,并且你希望在所有平台上都使用统一的行尾字符。你可以在 tsconfig.json 文件中设置 newLine 选项来实现这一点。
如果你希望使用 Unix 风格的行尾字符(即 LF),你可以这样配置:
{
"compilerOptions": {
"newLine": "LF",
// 其他配置...
}
}
然后,当你编译 TypeScript 代码时,TypeScript 编译器将会确保所有输出的 .js 文件都使用 LF 作为行尾字符,无论你的开发环境是 Windows、Linux 还是 macOS。
注意事项:
- 默认情况下,TypeScript 编译器会使用与你当前操作系统相匹配的行尾字符。因此,如果你在 Windows 上编译代码,它通常会使用 CRLF;在 Unix-like 系统上,它通常会使用 LF。
- 通过显式设置 newLine 选项,你可以覆盖这一默认行为,并确保跨平台的一致性。
- 这个选项只影响 TypeScript 编译器输出的文件。它不会更改你的 TypeScript 源文件或任何其他输入文件的行尾字符。
- 在团队开发环境中,统一行尾字符的样式可以帮助减少由于不同开发环境之间的细微差异而导致的合并冲突或其他问题。
扩展:在eslint中检查换行符的配置规则:linebreak-style
该规则有一个字符串选项:
- "unix" (默认) 强制使用 Unix 换行符: \n。
- "windows" 强制使用 Windows 换行符: \r\n。
noEmit(无输出文件)
noEmit 配置项用于控制 TypeScript 编译器是否应该输出编译后的文件,如 JavaScript 源代码、source-maps 或声明文件(.d.ts)。
当你设置 noEmit 为 true 时,TypeScript 编译器将不会生成这些输出文件。这通常在你想要使用其他工具(如 Babel 或 swc)来处理 TypeScript 文件到能在 JavaScript 环境中运行的代码时很有用。
即使你设置了 noEmit 为 true,TypeScript编译器仍然会执行类型检查。它只是不会生成输出文件而已。
noEmitHelpers
默认情况下,当 TypeScript 编译到不支持其某些特性的目标环境(如ES5)时,它会生成一些辅助函数来模拟这些特性。这些辅助函数会被导入到每个需要它们的文件中,这样可以避免在每个文件中重复生成相同的辅助函数。
然而,在某些情况下,你可能想要避免这些辅助函数的导入,而是希望将它们作为全局函数提供。这通常是为了减少生成的代码量,或者为了与其他工具(如压缩工具、打包工具)更好地集成。
当 noEmitHelpers 设置为 true 时,TypeScript 编译器将不会为辅助函数生成导入语句,而是假设这些辅助函数已经作为全局函数存在。这意味着你需要在运行时环境中提供这些函数的实现。
示例:
const getAPI = async (url: string) => {
// Get API
return {};
};
未开启noEmitHelpers时,生成的JS代码:
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (g && (g = 0, op[0] && (_ = 0)), _) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
var getAPI = function (url) { return __awaiter(void 0, void 0, void 0, function () {
return __generator(this, function (_a) {
// Get API
return [2 /*return*/, {}];
});
}); };
可以通过此标志与您自己的全局变量切换,配置noEmitHelpers:true,后,文件编译为:
"use strict";
var getAPI = function (url) { return __awaiter(void 0, void 0, void 0, function () {
return __generator(this, function (_a) {
// Get API
return [2 /*return*/, {}];
});
}); };
注意事项:
- 1.全局辅助函数的实现:当你设置 noEmitHelpers 为 true 时,你需要确保你的运行时环境中存在这些全局辅助函数的实现。否则,你的代码将无法正常工作。
- 2.与其他工具的集成:如果你使用了一些工具(如 Babel)来转换或优化你的代码,并且这些工具也处理了 TypeScript 的辅助函数,那么设置noEmitHelpers可能会与这些工具产生冲突。在这种情况下,你可能需要根据你的构建工具和配置来调整noEmitHelpers 的设置。
noEmitOnError(编译器报告错误不输出文件)
如果编译过程中报告了任何错误,那么不要生成编译器输出文件,如 JavaScript 源代码、源映射(source-maps)或声明文件(declarations)。
如果 noEmitOnError 设置为 false(默认值),即使编译过程中出现了错误,TypeScript 也会继续生成输出文件(JavaScript、源映射和声明文件)。
如果你将 noEmitOnError 设置为 true,那么只要编译过程中发现了任何错误,TypeScript 就不会生成任何输出文件。
outDir(编译器编译后的输出目录)
主要用于指定TypeScript编译器编译后的输出目录
outDir配置项用于告诉TypeScript编译器将编译后的文件(如JavaScript文件、声明文件等)输出到哪个目录。
示例,如下ts代码,默认输出在同目录下:
example
├── index.js
└── index.ts
当配置tsconfig.json中outDir配置项后:
tsconfig.json
{
"compilerOptions": {
"outDir": "dist"
}
}
最终编译后的目录结构:
example
├── dist
│ └── index.js
├── index.ts
└── tsconfig.json
outFile(非模块全局TS文件合并输出到一个单一文件中)
tsconfig.json 中的 outFile 配置项允许你将所有的全局(非模块)TypeScript 文件合并到一个单一的输出文件中。当与某些模块系统(如AMD)一起使用时,它还可以将模块文件合并到这个输出文件中。
注意事项:
- outFile 不能与 CommonJS 或 ES6 模块一起使用,因为这些模块系统通常使用动态导入和导出,这导致它们不适合静态合并。
- outFile 只能在 module 配置项设置为 None、System 或 AMD 时使用。
示例:
{
"compilerOptions": {
"target": "ES5",
"module": "AMD",
"outFile": "dist/bundle.js"
},
"files": [
"src/file1.ts",
"src/file2.ts",
"src/module1.ts", // 假设这是一个AMD模块
"src/module2.ts" // 假设这也是一个AMD模块
]
}
在这个例子中,file1.ts 和 file2.ts(假设它们不是模块)将会被合并到 dist/bundle.js 文件中。同时,由于 module 设置为 AMD,module1.ts 和 module2.ts(这两个文件应该是AMD模块)也会被合并到这个文件中。
如果你尝试将 module 设置为 CommonJS 或 ES6 并使用 outFile,TypeScript 编译器将会报错,因为它不支持将这些模块系统合并到单个文件中。
注意:选项“declarationDir”不能与选项“outFile”同时指定。
附files文件代码
src/file1.ts:
const name1: string = 'zhangsan'
const age1: number = 20
src/file2.ts:
function sum1(a: number,b: number): number {
return a + b;
}
src/module1.ts:
define(["require", "exports"], function (require, exports) {
function greet(name: string) {
console.log("Hello, " + name + "!");
}
exports.greet = greet;
});
function define(arg0: string[], arg1: (require: any, exports: any) => void) {
throw new Error("Function not implemented.");
}
src/module2.ts:
define(["require", "exports"], function (require, exports) {
function greet2(name: string) {
console.log("Hello2, " + name + "!");
}
exports.greet2 = greet2;
});
编译后dist/bundle.js代码:
"use strict";
var name1 = 'zhangsan';
var age1 = 20;
function sum1(a, b) {
return a + b;
}
define(["require", "exports"], function (require, exports) {
function greet(name) {
console.log("Hello, " + name + "!");
}
exports.greet = greet;
});
function define(arg0, arg1) {
throw new Error("Function not implemented.");
}
define(["require", "exports"], function (require, exports) {
function greet2(name) {
console.log("Hello2, " + name + "!");
}
exports.greet2 = greet2;
});
AMD定义:https://github.com/amdjs/amdjs-api/wiki/AMD
preserveConstEnums(保留枚举声明)
preserveConstEnums 配置项控制着 TypeScript 编译器如何处理 enum。enum 是一种特殊的枚举类型,它在编译时会被完全内联(即替换为其对应的值),从而避免在运行时产生额外的枚举对。
当 preserveConstEnums 设置为 true 时,TypeScript 编译器会保留 enum 的声明,而不是在生成的 JavaScript 代码中将其内联为值。这允许你在运行时仍然可以访问枚举的名称(尽管这通常不是 enum 的主要使用场景,因为其主要目的是为了减少运行时内存占用)。
示例,以下 TypeScript 代码:
const enum Album {
JimmyEatWorldFutures = 1,
TubRingZooHypothesis = 2,
DogFashionDiscoAdultery = 3,
}
const selectedAlbum = Album.JimmyEatWorldFutures;
if (selectedAlbum === Album.JimmyEatWorldFutures) {
console.log("That is a great choice.");
}
当 preserveConstEnums 为 false(或未设置)时,生成的 JavaScript 代码将不包含 Album 枚举的声明,而是直接将枚举值内联到代码中:
"use strict";
var selectedAlbum = 1 /* Album.JimmyEatWorldFutures */;
if (selectedAlbum === 1 /* Album.JimmyEatWorldFutures */) {
console.log("That is a great choice.");
}
当 preserveConstEnums 为 true 时,生成的 JavaScript 代码将包含 Album 枚举的声明:
"use strict";
var Album;
(function (Album) {
Album[Album["JimmyEatWorldFutures"] = 1] = "JimmyEatWorldFutures";
Album[Album["TubRingZooHypothesis"] = 2] = "TubRingZooHypothesis";
Album[Album["DogFashionDiscoAdultery"] = 3] = "DogFashionDiscoAdultery";
})(Album || (Album = {}));
var selectedAlbum = 1 /* Album.JimmyEatWorldFutures */;
if (selectedAlbum === 1 /* Album.JimmyEatWorldFutures */) {
console.log("That is a great choice.");
}
preserveValueImports(导入值保留)
preserveValueImports 是 TypeScript 中的一个旧配置选项,用于确保即使某些导入的值在编译后的 JavaScript 中看起来没有被直接使用,它们也会被保留在输出中。然而,这个选项已经被弃用(Deprecated),因为 TypeScript 社区认为更好的方法是直接显式地处理这些情况,而不是依赖于一个隐式的选项。
取而代之的是 verbatimModuleSyntax 选项,但这个选项本身也不是用来替代 preserveValueImports 的直接功能。
在某些情况下,TypeScript 无法检测到您正在使用导入。例如,以下代码:
import { Animal } from "./animal.js";
eval("console.log(new Animal().isDangerous())");
removeComments(删除注释)
如下示例代码:
/** The translation of 'Hello world' into Portuguese */
export const helloWorldPTBR = "Olá Mundo";
没有设置为或将其removeComments设置为false,编译后的结果:
/** The translation of 'Hello world' into Portuguese */
exports.helloWorldPTBR = "Olá Mundo";
当removeComments设置为true时:
exports.helloWorldPTBR = "Olá Mundo";
sourceMap(源代码映射文件)
示例代码
// helloWorld.ts
export declare const helloWorld = "hi";
当sourceMap设置为true时,编译后的JS代码:
// helloWorld.js
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.helloWorld = "hi";
//# sourceMappingURL=// helloWorld.js.map
生成的.js.map文件的JSON映射:
// helloWorld.js.map
{
"version": 3,
"file": "ex.js",
"sourceRoot": "",
"sources": ["../ex.ts"],
"names": [],
"mappings": ";;AAAa,QAAA,UAAU,GAAG,IAAI,CAAA"
}
sourceRoot(源文件根路径)
指定调试器应查找 TypeScript 文件的位置,而不是相对源位置。
tsconfig.json配置:
{
"compilerOptions": {
"sourceMap": true,
"sourceRoot": "https://my-website.com/debug/source/"
}
}
编译后 JS map文件内容如下:
{
"version": 3,
"file": "hello.js",
"sourceRoot": "https://my-website.com/debug/source/",
"sources": ["share-utils/hello.ts"],
"names": [],
"mappings": ";AAAA,IAAM,UAAU,GAAG,IAAI,CAAC;AACxB,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC"
}
stripInternal
当stripInternal 设置为 true,则 TypeScript 编译器在生成 .d.ts 声明文件时不会包含这些带有 @internal 标记的声明。
如下typescript代码:
/**
* Days available in a week
* @internal
*/
export const daysInAWeek = 7;
/** Calculate how much someone earns in a week */
export function weeklySalary(dayRate: number) {
return daysInAWeek * dayRate;
}
当不设置或者设置stripInternal为false时,生成的声明文件.d.ts代码如下:
/**
* Days available in a week
* @internal
*/
export declare const daysInAWeek = 7;
/** Calculate how much someone earns in a week */
export declare function weeklySalary(dayRate: number): number;
当设置stripInternal为true时,生成的声明文件.d.ts代码如下:
/** Calculate how much someone earns in a week */
export declare function weeklySalary(dayRate: number): number;
注:最终JS的输出文件内容是一样的。
JavaScript Support
allowJs
tsconfig.json 文件中的 allowJs 配置项确实用于允许在 TypeScript 项目中导入 JavaScript 文件。
示例:
当你有一个 JavaScript 文件 card.js:
// @filename: card.js
export const defaultCardDeck = "Heart";
并且你的 tsconfig.json 文件中没有启用 allowJs 选项时,尝试在 TypeScript 文件 index.ts 中导入它会导致编译错误:
// @filename: index.ts
import { defaultCardDeck } from "./card";
console.log(defaultCardDeck);
因为 TypeScript 默认不识别从 .js 文件中导出的内容。
如果你在 tsconfig.json 文件中启用了 allowJs 选项:
{
"compilerOptions": {
"allowJs": true,
// 其他选项...
},
// 其他配置...
}
那么上述的 TypeScript 文件 index.ts 将能够成功编译,因为 allowJs 允许了从 .js 文件中导入。
但请注意,这并不意味着 TypeScript 会将 .js 文件转换为 .ts 文件或者会检查 .js 文件中的类型错误。它仅仅允许 TypeScript 编译器识别 .js 文件中的 ES6 模块语法。
checkJs
checkJs 选项与 allowJs 选项配合使用。当 checkJs 被启用时,TypeScript 编译器会在 JavaScript 文件中报告类型错误。这相当于在项目的所有 JavaScript 文件顶部自动添加了 TypeScript 的类型检查注释 // @ts-check
示例:
假设你有以下的 JavaScript 文件和 TypeScript 文件:
constants.js:
// parseFloat 只接受一个字符串作为参数
module.exports.pi = parseFloat(3.142); // 这里有一个错误,因为 parseFloat 需要一个字符串
index.ts:
import { pi } from "./constants";
console.log(pi);
如果你没有启用 checkJs,当你尝试在 TypeScript 文件中导入这个 JavaScript 文件时,你不会得到任何错误提示。但是,如果你启用了 checkJs,你会在 constants.js 文件中得到一个类型错误,指出 parseFloat 的参数类型不正确,因为它期望一个字符串,但得到了一个数字。
启用 checkJs 后的错误消息:
constants.js: 参数类型 'number' 不能赋值给类型 'string' 的参数。
tsconfig.json配置:
{
"compilerOptions": {
"allowJs": true,
"checkJs": true,
// ... 其他配置项
},
// ... 其他配置
}
maxNodeModuleJsDepth
maxNodeModuleJsDepth: 指定在 node_modules 目录下搜索并加载 JavaScript 文件的最大依赖深度。
此标志仅在启用 allowJs 时可用,当你希望 TypeScript 为 .node_modules 中的所有 JavaScript 代码推断类型时使用此选项。
理想情况下,此值应保持为 0(默认值),并应使用声明文件(.d.ts)来显式定义模块的形状。然而,在某些情况下,你可能需要以牺牲速度和潜在准确性为代价,选择开启此选项并设置一个大于0的值。
场景示例:
假设有这样一个项目结构,你正在使用一个名为 fantastic-lib 的第三方 JavaScript 库,它不提供 TypeScript 类型定义文件(.d.ts),但你想在 TypeScript 项目中直接使用它而不报错,同时这个库内部还依赖了其他几个深层次的 JavaScript 库。
结构如下:
my-project/
│
├── node_modules/
│ ├── fantastic-lib/ # 第三方 JS 库,无类型定义
│ │ ├── index.js
│ │ └── ...
│ │
│ ├── sublib-a/ # 被 fantastic-lib 依赖的库A,同样无类型定义
│ │ ├── index.js
│ │ └── ...
│ └── ...
│
├── src/
│ └── index.ts # 你的 TypeScript 代码
│
└── tsconfig.json # TypeScript 配置文件
默认行为(maxNodeModuleJsDepth = 0)
默认情况下,TypeScript 不会深入 node_modules 目录去查找并分析 JavaScript 文件来推断类型。如果你在 index.ts 中尝试导入 fantastic-lib,而它没有类型定义,TypeScript编译器会报错,除非你提供了相应的类型声明。
修改配置(maxNodeModuleJsDepth > 0)
为了使 TypeScript 在一定程度上能理解和推断这些未提供类型定义的库的接口,你可以调整 maxNodeModuleJsDepth, tsconfig.json 配置如下:
{
"compilerOptions": {
"allowJs": true, // 必须开启,允许编译器处理 JavaScript 文件
"maxNodeModuleJsDepth": 2 // 设置最大搜索深度为2,表示会查找两层深度的 .js 文件
}
}
说明:
- 当设置了 maxNodeModuleJsDepth: 2,TypeScript 编译器不仅会尝试直接分析 fantastic-lib 的 JavaScript 代码来推断类型,还会深入到它直接依赖的库(如 sublib-a),直到达到指定的深度(这里是两层)。
- 这个设置在牺牲编译速度和可能的准确性(因为类型推断不如直接提供的类型定义精确)的同时,为你提供了对未提供类型定义的 JavaScript 库的基本使用能力。
- 注意,这种方法并不总是理想的解决方案,最佳实践仍然是寻找或创建准确的类型定义文件(.d.ts),但当这是唯一选项时,maxNodeModuleJsDepth 提供了一个临时的解决方案。
注:在某些情况下,如果你遇到了与 node_modules 解析相关的问题,并且怀疑是由于目录深度导致的,那么可以尝试增加 maxNodeModuleJsDepth 的值来解决问题。
Interop Constraints(操作约束)
allowSyntheticDefaultImports(允许合成默认导入)
tsconfig.json 配置文件中的 allowSyntheticDefaultImports 选项的作用是允许你在模块没有明确指定默认导出(default export)的情况下,使用类似于默认导出的语法来导入模块。这主要是为了与一些像 Babel 这样的转译器(transpiler)的行为保持一致,这些转译器可能会自动为模块添加一个默认导出。
当allowSyntheticDefaultImports设置为 true 时,这个选项允许你像下面这样导入模块:
import React from "react";
而不是:
import * as React from "react";
即使模块本身并没有明确指定一个默认导出。
如下示例,如果没有设置allowSyntheticDefaultImports。
JavaScript 模块 utilFunctions.js:
// @filename: utilFunctions.js
const getStringLength = (str) => str.length;
module.exports = {
getStringLength,
};
在 TypeScript 中,如果你尝试使用默认导入的方式:
// @filename: index.ts
import utils from "./utilFunctions"; // 这会报错
const count = utils.getStringLength("Check JS");
你会得到一个错误,因为 utilFunctions.js 没有默认导出。然而,如果你设置了 allowSyntheticDefaultImports 为 true,TypeScript 的类型检查器会允许这种导入语法。
为方便起见,像 Babel 这样的转译器会自动创建一个默认值(如果未创建)。使模块看起来更像:
// @filename: utilFunctions.js
const getStringLength = (str) => str.length;
const allFunctions = {
getStringLength,
};
module.exports = allFunctions;
module.exports.default = allFunctions;
注意:
- 这个选项并不会影响 TypeScript 生成的 JavaScript 代码。它仅用于类型检查。
- 这个选项使得 TypeScript 的行为与 Babel 一致,Babel 会在转译过程中为模块添加一个默认的导出,以便更方便地使用默认导入的语法。
esModuleInterop(支持es module和commonjs混用)
先上结论:开启esModuleInterop作用:支持es module 和 commonjs 混合使用
默认情况下(即设置为 false 或未设置),TypeScript 将 CommonJS/AMD/UMD 模块视为类似于 ES6 模块。但在这样做时,有两个特定的部分被认为是错误的假设:
- 1、命名空间导入(如 import * as moment from "moment")的行为与 const moment = require("moment") 相同。
- 2、默认导入(如 import moment from "moment")的行为与 const moment = require("moment").default 相同。
这种不匹配导致了两个问题:
- ES6 模块规范规定,命名空间导入(import * as ...)只能是一个对象。但是,由于 TypeScript 将其视为与 require 相同的处理,TypeScript 允许将导入作为函数来调用,这是不符合规范的。
- 虽然与 ES6 模块规范相符,但大多数具有 CommonJS/AMD/UMD 模块的库并没有像 TypeScript 的实现那样严格地遵守规范。
启用 esModuleInterop 将修复 TypeScript 编译的代码中的这两个问题。第一个问题是通过改变编译器的行为来解决的,第二个问题是通过两个新的辅助函数来解决的,这些函数提供了一个 shim 来确保生成的 JavaScript 的兼容性。
当你启用了 esModuleInterop 选项,TypeScript 会尝试以一种更灵活的方式处理模块导入,以更好地兼容现有的 CommonJS/AMD/UMD 模块生态系统。这特别有助于当你尝试从使用这些模块系统的库导入时。
- 命名空间导入:启用 esModuleInterop 后,TypeScript 不会强制将命名空间导入视为对象。这意味着你可以像处理对象一样使用它们,但如果你尝试调用它们(即使它们不是函数),TypeScript 也不会报错。
- 默认导入:对于默认导入,TypeScript 会尝试从 CommonJS/AMD/UMD 模块中查找 .default 属性(如果存在)。但是,由于许多库并没有导出 .default,TypeScript 会使用一个 shim 来确保你可以像使用 ES6 模块一样使用默认导入。
如下示例代码:
import * as fs from "fs";
import _ from "lodash";
fs.readFileSync("file.txt", "utf8");
_.chunk(["a", "b", "c", "d"], 2);
禁用esModuleInterop时,生成的JS代码:
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var fs = require("fs");
var lodash_1 = require("lodash");
fs.readFileSync("file.txt", "utf8");
lodash_1.default.chunk(["a", "b", "c", "d"], 2);
启用esModuleInterop时,生成的JS代码(结合 importHelpers配置,):
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var tslib_1 = require("tslib");
var fs = tslib_1.__importStar(require("fs"));
var lodash_1 = tslib_1.__importDefault(require("lodash"));
fs.readFileSync("file.txt", "utf8");
lodash_1.default.chunk(["a", "b", "c", "d"], 2);
forceConsistentCasingInFileNames(区分文件名大小写)
forceConsistentCasingInFileNames 是 tsconfig.json 中的一个配置项,它用于确保 TypeScript 在编译时使用的文件名大小写与磁盘上的实际文件名大小写保持一致。这个选项在跨平台开发时特别有用,因为不同的操作系统对文件名的大小写敏感性有不同的处理方式。
例如,在 Windows 上,fileManager.ts 和 FileManager.ts 被视为同一个文件,但在 macOS 或 Linux 上,它们则是两个不同的文件。如果 TypeScript 编译器在 Windows 上编译了一个引用 fileManager.ts 的文件,而实际磁盘上的文件名是 FileManager.ts,那么这个程序在 macOS 或 Linux 上运行时就会找不到 fileManager.ts 这个文件,导致运行时错误。
通过设置 forceConsistentCasingInFileNames 为 true,TypeScript 编译器会在编译时检查所有文件引用的大小写是否与磁盘上的实际文件名大小写一致。如果不一致,编译器就会报错
isolatedModules(隔离模块)
tsconfig.json 中的 isolatedModules 配置项是一个非常重要的选项,特别是在与其他 JavaScript 转换工具(如 Babel)一起使用时。这个配置项告诉 TypeScript编译器在编译时考虑单文件编译的限制,因为不是所有的 TypeScript功能都能在单文件编译环境中正确工作。
isolatedModules 的作用:
- 确保兼容性:当使用只支持单文件编译的工具(如 Babel)时,启用 isolatedModules 可以确保 TypeScript 代码与这些工具兼容。
- 避免运行时错误:由于单文件编译工具不能考虑整个类型系统,因此某些 TypeScript 功能在单文件编译时可能会导致运行时错误。isolatedModules 会在这些情况下发出警告。
示例
导出非值标识符
在 TypeScript 中,你可以导入一个类型然后导出它。但是,在单文件编译环境中,如果一个类型没有与之关联的值,那么它就不会被导出(因为 JavaScript 不支持类型导出)。
// 假设 isolatedModules 是启用的
import { someType, someFunction } from "someModule";
someFunction();
// 这会导致错误,因为 someType 是一个类型,没有与之关联的值
export { someType, someFunction }; // 错误
// 应该只导出有值的标识符
export { someFunction };
非模块文件
如果启用了 isolatedModules,则只允许在模块中使用命名空间(即文件必须包含某种形式的 import 或 export)。如果在非模块文件中发现命名空间,则会引发错误。
// 假设 isolatedModules 是启用的
// 以下代码会导致错误,因为这是一个非模块文件,但包含了命名空间
namespace Instantiated {
export const x = 1;
}
// 要修复此错误,你需要将此文件转换为模块
// 例如,通过添加一个空的 'export {}' 语句
export {}; // 将文件转换为模块
对枚举成员的引用
declare const enum Numbers {
Zero = 0,
One = 1,
}
console.log(Numbers.Zero + Numbers.One);
当没有开启isolatedModules时,上述代码编译为:
"use strict";
console.log(0 /* Numbers.Zero */ + 1 /* Numbers.One */);
开启了isolatedModules时,上述代码对枚举成员的引用就会报错,报错信息为:启用“isolatedModules”时无法访问环境常量枚举。
上面代码调整为普通的枚举,把const enum 调整为enum,如下示例:
enum Numbers {
Zero = 0,
One = 1,
}
console.log(Numbers.Zero + Numbers.One);
最终编译后的结果为:
"use strict";
var Numbers;
(function (Numbers) {
Numbers[Numbers["Zero"] = 0] = "Zero";
Numbers[Numbers["One"] = 1] = "One";
})(Numbers || (Numbers = {}));
console.log(Numbers.Zero + Numbers.One);
注:isolatedModules 并不改变 TypeScript 代码的行为或 TypeScript 的检查和输出过程的行为。它只是增加了额外的检查来确保与单文件编译环境的兼容性。
preserveSymlinks(保留符号链接)
略
verbatimModuleSyntax
当你设置了 verbatimModuleSyntax 为 true 后,TypeScript 将会输出类似下面的代码(假设原 TypeScript 代码中有类型导入):
// TypeScript 源码
import { Car } from "./car";
export function drive(car: Car) {
// ...
}
// 生成的 JavaScript 代码(当 verbatimModuleSyntax 为 true)
import { Car } from "./car"; // 导入被保留
export function drive(car) {
// ...
}
verbatimModuleSyntax 选项允许你告诉 TypeScript 编译器不要进行导入省略,而是保留所有的 import 和 export 语句。当设置为 true 时,TypeScript 将会原样输出模块语法,不进行任何省略。
Language and Environment(语言环境)
emitDecoratorMetadata
略
experimentalDecorators
略
启用对装饰器的实验性支持,装饰器是早于 TC39 标准化过程的装饰器版本。
装饰器是一种尚未完全被批准到 JavaScript 规范中的语言功能。
jsx
在tsconfig.json中,jsx配置项决定了在TypeScript文件中使用的JSX语法在编译到JavaScript文件时如何被转换。这个选项只对.tsx文件的输出有影响,即那些包含JSX语法的TypeScript文件。
jsx配置项中各个值的解释:
- react: 这是默认值,它会将JSX语法转换为React.createElement的调用。这是React 16及更早版本的标准转换方式。
- react-jsx: React 17引入了这个新的转换模式,它将JSX转换为对jsx函数的调用,该函数默认从react/jsx-runtime导入。这种模式提供了更小的包体积和更好的Tree Shaking。
- react-jsxdev: 这也是React 17引入的,与react-jsx类似,但它将JSX转换为对jsxDEV函数的调用,该函数默认从react/jsx-dev-runtime导入。这个模式主要用于开发环境,因为它提供了额外的运行时检查和开发帮助。
- preserve: 这将保留JSX语法不变,也就是说,.tsx文件将被编译为包含JSX语法的.js文件。这种模式通常与Babel等其他工具一起使用,这些工具可以在后续步骤中处理JSX。
- react-native: 这与preserve类似,但它是专门为React Native设计的。在React Native中,JSX语法被直接支持,因此不需要转换为React.createElement或其他函数。
如下示例,.jsx代码:
export const HelloWorld = () => <h1>Hello world</h1>;
Default: "react":
import React from 'react';
export const HelloWorld = () => React.createElement("h1", null, "Hello world");
Preserve: "preserve":
import React from 'react';
export const HelloWorld = () => <h1>Hello world</h1>;
React Native: "react-native":
import React from 'react';
export const HelloWorld = () => <h1>Hello world</h1>;
React 17 transform: "react-jsx":
import { jsx as _jsx } from "react/jsx-runtime";
export const HelloWorld = () => _jsx("h1", { children: "Hello world" });
React 17 dev transform: "react-jsxdev":
import { jsxDEV as _jsxDEV } from "react/jsx-dev-runtime";
const _jsxFileName = "/home/runner/work/TypeScript-Website/TypeScript-Website/packages/typescriptlang-org/index.tsx";
export const HelloWorld = () => _jsxDEV("h1", { children: "Hello world" }, void 0, false, { fileName: _jsxFileName, lineNumber: 9, columnNumber: 32 }, this);
jsxFactory
jsxFactory 配置项用于更改在使用经典 JSX 运行时编译 JSX 元素时,在文件中调用的函数。最常见的改动是为了使用像 preact.h 而非默认的 React.createElement,尤其是在使用诸如 Preact 这样的库时。
示例配置及代码:
给定以下 TSX 文件示例:
import { h } from "preact";
const HelloWorld = () => <div>Hello</div>;
当在 tsconfig.json 中设置 jsxFactory 为 "h":
{
"compilerOptions": {
"jsxFactory": "h"
}
}
编译后的 JavaScript 代码将会是:
const preact_1 = require("preact");
const HelloWorld = () => (0, preact_1.h)("div", null, "Hello");
这个选项也可以像 Babel 的 /** @jsx h */ 指令那样在每个文件基础上使用。
/** @jsx h */
import { h } from "preact";
const HelloWorld = () => <div>Hello</div>;
注意这里的提示信息 “Cannot find module 'preact' or its corresponding type declarations.” 表明在实际环境中需要确保已安装 preact 及其类型定义。
工厂函数对命名空间查找的影响:
所选的工厂函数也会影响类型检查信息的命名空间查找顺序。例如,如果工厂函数被定义为默认的 React.createElement,编译器会先查找 React.JSX 命名空间,然后才是全局的 JSX 命名空间。如果工厂函数被定义为 h,它将首先查找 hh.JSX 命名空间,之后才是全局的 JSX 命名空间。这样可以确保类型信息能够正确关联到你所使用的库。
Preact是一个只有3KB大小的库,它与React拥有相同的API,因此可以作为React的轻量级替代方案。Preact的名字来源于"Performance"和"React"的结合,强调了其高性能的特性。Preact关注于React的核心功能,实现了一套简单可预测的diff算法,使其成为最快的虚拟DOM框架之一。
jsxFragmentFactory
jsxFragmentFactory 配置项用于指定在将 TypeScript JSX 代码编译为目标(如 React)时使用的 JSX 片段工厂函数,特别是在已经通过 jsxFactory 指定了 JSX 工厂函数的情况下。例如,可以指定 .Fragment 作为片段工厂。
示例配置
给定以下 tsconfig.json 配置:
{
"compilerOptions": {
"target": "esnext", // 目标 ES 版本为最新的 ES 特性
"module": "commonjs", // 使用 CommonJS 模块系统
"jsx": "react", // 启用 JSX 并指定为 React 风格
"jsxFactory": "h", // 指定 JSX 工厂函数为 "h"
"jsxFragmentFactory": "Fragment" // 指定 JSX 片段工厂函数为 "Fragment"
}
}
TSX 文件示例:
import { h, Fragment } from "preact";
const HelloWorld = () => (
<>
<div>Hello</div>
</>
);
编译后的 JavaScript 示例:
const preact_1 = require("preact");
const HelloWorld = () => ((0, preact_1.h)(preact_1.Fragment, null,
(0, preact_1.h)("div", null, "Hello")));
在这个例子中,TypeScript 编译器使用了配置中指定的 jsxFactory (h) 和 jsxFragmentFactory (Fragment) 来处理 JSX 代码。原本的片段 <></> 被转换成使用 preact_1.Fragment 和 preact_1.h 函数的形式。
此选项也可以在每个文件基础上使用,类似于 Babel 的 /* @jsxFrag h */ 指令。
文件内指令示例:
/** @jsx h */
/** @jsxFrag Fragment */
import { h, Fragment } from "preact";
// ...(此处的代码将类似上面的示例进行编译)
请注意,上述代码示例中出现的 "Cannot find module 'preact' or its corresponding type declarations." 是一个提示信息,表明在实际运行或查看代码时需要确保已正确安装 preact 及其类型定义。
jsxImportSource
jsxImportSource 配置项声明了在使用 jsx 为 "react-jsx" 或 "react-jsxdev"(TypeScript 4.1 引入的新特性)时,用于导入 JSX 和工厂函数的模块指定符。
随着 React 17 版本的发布,库支持了一种新的 JSX 转换形式,即通过单独的导入实现。
示例代码及配置
考虑以下 TSX 代码:
import React from "react";
function App() {
return <h1>Hello World</h1>;
}
当在 tsconfig.json 中这样配置:
{
"compilerOptions": {
"target": "esnext",
"module": "commonjs",
"jsx": "react-jsx"
}
}
TypeScript 编译出的 JavaScript 代码为:
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const jsx_runtime_1 = require("react/jsx-runtime");
function App() {
return (0, jsx_runtime_1.jsx)("h1", { children: "Hello World" });
}
如果你想使用 Preact,你需要在 tsconfig.json 中这样配置:
{
"compilerOptions": {
"target": "esnext",
"module": "commonjs",
"jsx": "react-jsx",
"jsxImportSource": "preact",
"types": ["preact"]
}
}
这将生成类似的代码:
function App() {
return (0, jsx_runtime_1.jsx)("h1", { children: "Hello World" });
}
exports.App = App;
你也可以使用每文件的指令来设置此选项,例如:
/** @jsxImportSource preact */
export function App() {
return <h1>Hello World</h1>;
}
这将在代码中自动添加对 preact/jsx-runtime 的导入作为工厂函数来源。
注意:为了让此功能按预期工作,你的文件必须包含 export 或 import 语句,以便被识别为模块。在 .tsx 文件中,通常已经有默认的 export 或 import 语句,因此这通常不是问题。
lib
TypeScript 内置了一套针对 JavaScript 原生 API(如 Math)的标准类型定义,以及浏览器环境中的特性(如 document 和 Map)。此外,TypeScript 还会根据你指定的目标版本(target)包含对应的新 JavaScript 特性定义;例如,如果目标是 ES6 或更新的版本,就会包含 Promise 的定义。
你可能需要调整这些默认库的原因包括:
- 你的程序不在浏览器环境中运行,因此不需要 dom 类型定义。
- 运行时平台提供了某些 JavaScript API 对象(可能是通过polyfills),但尚未完全支持特定ECMAScript版本的语法。
- 对于较高级别的ECMAScript版本,你有部分polyfills或原生实现,但并非全部。
自 TypeScript 4.5 起,可以通过npm模块覆盖lib文件,详情可参考官方博客。
高级别库:
- ES5:包含所有ES3和ES5功能的核心定义。
- ES2015(也称为ES6):额外的API,如 Array.find, Promise, Proxy, Symbol, Map, Set, Reflect 等。
- ES6:是 "ES2015" 的别名。
- ES2016:新增 Array.includes 等API。
- ES7:是 "ES2016" 的别名。
- ES2017:引入了 Object.entries, Object.values, Atomics, SharedArrayBuffer, Date.prototype.formatToParts 等API。
- ES2018:新增迭代器、Promise.prototype.finally, Intl.PluralRules, RegExp.prototype.groups 等API。
- ES2019:引入了 Array.prototype.flat, Array.prototype.flatMap, Object.fromEntries, String.prototype.trimStart, String.prototype.trimEnd 等API。
- ES2020:增加了 String.prototype.matchAll 等API。
- ES2021:引入了 Promise.any, String.prototype.replaceAll 等API。
- ES2022:新增 Array.prototype.at, RegExp.prototype.hasIndices 等API。
- ESNext:随JavaScript规范演进而变化的额外API。
- DOM:包含 window, document 等DOM相关定义。
- WebWorker:Web Worker上下文可用的API。
- ScriptHost:Windows脚本宿主系统API。
独立库组件: 列表包含从 DOM.Iterable 到 ESNext.Symbol等具体API分类,允许你更细致地控制引入哪些特定API的类型定义。
示例,假设你正在开发一个Node.js应用,不涉及任何浏览器API,你可能希望在 tsconfig.json 中排除 "dom" 库,同时确保包含了你的Node.js版本支持的ES特性库:
{
"compilerOptions": {
"target": "es2017",
"module": "commonjs",
"lib": ["ES2017", "ES2018.Promise", "ScriptHost"]
}
}
在这个例子中,我们指定了目标为 es2017,并包含了 ES2017 标准库、ES2018.Promise(因为即使在ES2017目标下,我们仍可能想使用Promise的最新API),以及 ScriptHost 来支持Node.js环境。
注意:上述库列表可能不是最新版本,请查看TypeScript源代码获取完整且最新的库列表。
moduleDetection(模块检测)
moduleDetection 配置项控制了TypeScript如何判断一个文件是脚本(script)还是模块(module)。
该设置有三个可选项:
- "auto"(默认)- TypeScript不仅仅查找 import 和 export 语句,还会在使用 "module": "node" 或 "module": "nodenext" 且运行时,检查 package.json 文件中的 "type" 字段是否设置为 "module";同时,在使用 "jsx": "react-jsx" 进行编译时,也会检查当前文件是否为一个JSX文件。
- "legacy" - 行为与4.6及之前版本相同,仅依靠 import 和 export 语句来决定一个文件是否是模块。
- "force" - 确保所有非声明文件都被当作模块处理。
示例
假设你的项目混合使用了 CommonJS 和 ES 模块,并且你希望在使用 "module": "nodenext" 时,TypeScript 能够根据 package.json 中的 "type" 字段自动识别模块类型,你可以在 tsconfig.json 中这样配置:
{
"compilerOptions": {
"module": "nodenext",
"moduleDetection": "auto"
}
}
如果 package.json 文件中定义为:
{
"type": "module"
}
那么 TypeScript 将会正确地识别那些没有显式 import 或 export 语句但被标记为 "type": "module" 的文件为 ES 模块。
而若你希望无论文件内容如何,一律强制将所有源文件视为模块,可以设置:
{
"compilerOptions": {
"module": "commonjs",
"moduleDetection": "force"
}
}
这样,即使文件中没有导出或导入语句,TypeScript 也会将其视作模块处理,这在你想要确保一致的模块化处理逻辑时非常有用。
noLib
noLib 配置项用于禁用自动包含任何库文件。如果设置了此选项,lib 配置将被忽略。
TypeScript 编译器在没有为关键原始类型(如 Array、Boolean、Function、IArguments、Number、Object、RegExp、String 等)提供接口集的情况下无法进行编译。当你使用 noLib 时,预期你自己会提供这些基本类型的类型定义。
示例场景
假设你正在开发一个高度定制化的环境或框架,其中一些基本类型的默认行为或定义需要被重写,这时你可能会用到 noLib。你将手动引入或自定义这些核心类型库,以确保与你的环境完全兼容。
tsconfig.json 示例,不使用 noLib 的典型配置会是这样的:
{
"compilerOptions": {
"lib": ["es2015", "dom"]
}
}
但如果你决定禁用自动库包含并提供自己的类型定义,配置可能是这样:
{
"compilerOptions": {
"noLib": true
}
}
接下来,你需要在项目中显式地包含或创建必要的类型定义文件,例如创建一个 custom.global.d.ts 文件:
// custom.global.d.ts
interface String {
customMethod(): void;
}
declare global {
declare interface Array<T> {
customArrayMethod(callbackfn:(value: T, index: number, array: T[]) => void, thisArg?: any): void;
}
// ...其他自定义类型定义
}
这样,TypeScript 编译器会使用你提供的类型定义而不是默认的库文件,确保你的项目能够符合特定环境的需求。但请注意,这样做会增加维护成本,因为需要手动管理所有基础类型的定义。
上述代码定义了一个扩展了内置 String 类型的接口。它添加了一个名为 customMethod 的方法,该方法没有参数并且返回类型为 void。
declare global 块用来声明全局作用域内的类型。这意味着你在此块中声明的类型或变量会成为全局可用,对整个项目范围有效,而不仅仅是文件内部。在 global 块内部,再次声明了一个接口,这次是扩展了泛型 Array
我们具体怎样用到如上面申明的的customArrayMethod函数呢,我们在一个index.ts文件如下使用,由于我们只是对它进行了类型声明,如果要使用的话,需要定义该函数:
/// <reference path="./utils/global-l.d.ts" />
if(!Array.prototype.customArrayMethod) {
Array.prototype.customArrayMethod = function(): void {
// 实现你的方法逻辑
console.log('Custom array method called on', this);
};
}
const myArray:number[] = [1, 2, 3];
myArray.customArrayMethod(item => console.log(item));
myArray.forEach(item => console.log(item));
注: 上面代码中我们使用三斜杠(///
)指令显式引用该类型声明文件。这能确保类型信息在编译时被正确加载。
附
TypeScript关于lib.es5.d.ts关于String的声明:
interface String {
/** Returns a string representation of a string. */
toString(): string;
/**
* Returns the character at the specified index.
* @param pos The zero-based index of the desired character.
*/
charAt(pos: number): string;
/**
* Returns the Unicode value of the character at the specified location.
* @param index The zero-based index of the desired character. If there is no character at the specified index, NaN is returned.
*/
charCodeAt(index: number): number;
/**
* Returns a string that contains the concatenation of two or more strings.
* @param strings The strings to append to the end of the string.
*/
concat(...strings: string[]): string;
/**
* Returns the position of the first occurrence of a substring.
* @param searchString The substring to search for in the string
* @param position The index at which to begin searching the String object. If omitted, search starts at the beginning of the string.
*/
indexOf(searchString: string, position?: number): number;
/**
* Returns the last occurrence of a substring in the string.
* @param searchString The substring to search for.
* @param position The index at which to begin searching. If omitted, the search begins at the end of the string.
*/
lastIndexOf(searchString: string, position?: number): number;
/**
* Determines whether two strings are equivalent in the current locale.
* @param that String to compare to target string
*/
localeCompare(that: string): number;
/**
* Matches a string with a regular expression, and returns an array containing the results of that search.
* @param regexp A variable name or string literal containing the regular expression pattern and flags.
*/
match(regexp: string | RegExp): RegExpMatchArray | null;
/**
* Replaces text in a string, using a regular expression or search string.
* @param searchValue A string or regular expression to search for.
* @param replaceValue A string containing the text to replace. When the {@linkcode searchValue} is a `RegExp`, all matches are replaced if the `g` flag is set (or only those matches at the beginning, if the `y` flag is also present). Otherwise, only the first match of {@linkcode searchValue} is replaced.
*/
replace(searchValue: string | RegExp, replaceValue: string): string;
/**
* Replaces text in a string, using a regular expression or search string.
* @param searchValue A string to search for.
* @param replacer A function that returns the replacement text.
*/
replace(searchValue: string | RegExp, replacer: (substring: string, ...args: any[]) => string): string;
/**
* Finds the first substring match in a regular expression search.
* @param regexp The regular expression pattern and applicable flags.
*/
search(regexp: string | RegExp): number;
/**
* Returns a section of a string.
* @param start The index to the beginning of the specified portion of stringObj.
* @param end The index to the end of the specified portion of stringObj. The substring includes the characters up to, but not including, the character indicated by end.
* If this value is not specified, the substring continues to the end of stringObj.
*/
slice(start?: number, end?: number): string;
/**
* Split a string into substrings using the specified separator and return them as an array.
* @param separator A string that identifies character or characters to use in separating the string. If omitted, a single-element array containing the entire string is returned.
* @param limit A value used to limit the number of elements returned in the array.
*/
split(separator: string | RegExp, limit?: number): string[];
/**
* Returns the substring at the specified location within a String object.
* @param start The zero-based index number indicating the beginning of the substring.
* @param end Zero-based index number indicating the end of the substring. The substring includes the characters up to, but not including, the character indicated by end.
* If end is omitted, the characters from start through the end of the original string are returned.
*/
substring(start: number, end?: number): string;
/** Converts all the alphabetic characters in a string to lowercase. */
toLowerCase(): string;
/** Converts all alphabetic characters to lowercase, taking into account the host environment's current locale. */
toLocaleLowerCase(locales?: string | string[]): string;
/** Converts all the alphabetic characters in a string to uppercase. */
toUpperCase(): string;
/** Returns a string where all alphabetic characters have been converted to uppercase, taking into account the host environment's current locale. */
toLocaleUpperCase(locales?: string | string[]): string;
/** Removes the leading and trailing white space and line terminator characters from a string. */
trim(): string;
/** Returns the length of a String object. */
readonly length: number;
// IE extensions
/**
* Gets a substring beginning at the specified location and having the specified length.
* @deprecated A legacy feature for browser compatibility
* @param from The starting position of the desired substring. The index of the first character in the string is zero.
* @param length The number of characters to include in the returned substring.
*/
substr(from: number, length?: number): string;
/** Returns the primitive value of the specified object. */
valueOf(): string;
readonly [index: number]: string;
}
TypeScript关于lib.es5.d.ts关于Array的声明:
interface Array<T> {
/**
* Gets or sets the length of the array. This is a number one higher than the highest index in the array.
*/
length: number;
/**
* Returns a string representation of an array.
*/
toString(): string;
/**
* Returns a string representation of an array. The elements are converted to string using their toLocaleString methods.
*/
toLocaleString(): string;
/**
* Removes the last element from an array and returns it.
* If the array is empty, undefined is returned and the array is not modified.
*/
pop(): T | undefined;
/**
* Appends new elements to the end of an array, and returns the new length of the array.
* @param items New elements to add to the array.
*/
push(...items: T[]): number;
/**
* Combines two or more arrays.
* This method returns a new array without modifying any existing arrays.
* @param items Additional arrays and/or items to add to the end of the array.
*/
concat(...items: ConcatArray<T>[]): T[];
/**
* Combines two or more arrays.
* This method returns a new array without modifying any existing arrays.
* @param items Additional arrays and/or items to add to the end of the array.
*/
concat(...items: (T | ConcatArray<T>)[]): T[];
/**
* Adds all the elements of an array into a string, separated by the specified separator string.
* @param separator A string used to separate one element of the array from the next in the resulting string. If omitted, the array elements are separated with a comma.
*/
join(separator?: string): string;
/**
* Reverses the elements in an array in place.
* This method mutates the array and returns a reference to the same array.
*/
reverse(): T[];
/**
* Removes the first element from an array and returns it.
* If the array is empty, undefined is returned and the array is not modified.
*/
shift(): T | undefined;
/**
* Returns a copy of a section of an array.
* For both start and end, a negative index can be used to indicate an offset from the end of the array.
* For example, -2 refers to the second to last element of the array.
* @param start The beginning index of the specified portion of the array.
* If start is undefined, then the slice begins at index 0.
* @param end The end index of the specified portion of the array. This is exclusive of the element at the index 'end'.
* If end is undefined, then the slice extends to the end of the array.
*/
slice(start?: number, end?: number): T[];
/**
* Sorts an array in place.
* This method mutates the array and returns a reference to the same array.
* @param compareFn Function used to determine the order of the elements. It is expected to return
* a negative value if the first argument is less than the second argument, zero if they're equal, and a positive
* value otherwise. If omitted, the elements are sorted in ascending, ASCII character order.
* ```ts
* [11,2,22,1].sort((a, b) => a - b)
* ```
*/
sort(compareFn?: (a: T, b: T) => number): this;
/**
* Removes elements from an array and, if necessary, inserts new elements in their place, returning the deleted elements.
* @param start The zero-based location in the array from which to start removing elements.
* @param deleteCount The number of elements to remove.
* @returns An array containing the elements that were deleted.
*/
splice(start: number, deleteCount?: number): T[];
/**
* Removes elements from an array and, if necessary, inserts new elements in their place, returning the deleted elements.
* @param start The zero-based location in the array from which to start removing elements.
* @param deleteCount The number of elements to remove.
* @param items Elements to insert into the array in place of the deleted elements.
* @returns An array containing the elements that were deleted.
*/
splice(start: number, deleteCount: number, ...items: T[]): T[];
/**
* Inserts new elements at the start of an array, and returns the new length of the array.
* @param items Elements to insert at the start of the array.
*/
unshift(...items: T[]): number;
/**
* Returns the index of the first occurrence of a value in an array, or -1 if it is not present.
* @param searchElement The value to locate in the array.
* @param fromIndex The array index at which to begin the search. If fromIndex is omitted, the search starts at index 0.
*/
indexOf(searchElement: T, fromIndex?: number): number;
/**
* Returns the index of the last occurrence of a specified value in an array, or -1 if it is not present.
* @param searchElement The value to locate in the array.
* @param fromIndex The array index at which to begin searching backward. If fromIndex is omitted, the search starts at the last index in the array.
*/
lastIndexOf(searchElement: T, fromIndex?: number): number;
/**
* Determines whether all the members of an array satisfy the specified test.
* @param predicate A function that accepts up to three arguments. The every method calls
* the predicate function for each element in the array until the predicate returns a value
* which is coercible to the Boolean value false, or until the end of the array.
* @param thisArg An object to which the this keyword can refer in the predicate function.
* If thisArg is omitted, undefined is used as the this value.
*/
every<S extends T>(predicate: (value: T, index: number, array: T[]) => value is S, thisArg?: any): this is S[];
/**
* Determines whether all the members of an array satisfy the specified test.
* @param predicate A function that accepts up to three arguments. The every method calls
* the predicate function for each element in the array until the predicate returns a value
* which is coercible to the Boolean value false, or until the end of the array.
* @param thisArg An object to which the this keyword can refer in the predicate function.
* If thisArg is omitted, undefined is used as the this value.
*/
every(predicate: (value: T, index: number, array: T[]) => unknown, thisArg?: any): boolean;
/**
* Determines whether the specified callback function returns true for any element of an array.
* @param predicate A function that accepts up to three arguments. The some method calls
* the predicate function for each element in the array until the predicate returns a value
* which is coercible to the Boolean value true, or until the end of the array.
* @param thisArg An object to which the this keyword can refer in the predicate function.
* If thisArg is omitted, undefined is used as the this value.
*/
some(predicate: (value: T, index: number, array: T[]) => unknown, thisArg?: any): boolean;
/**
* Performs the specified action for each element in an array.
* @param callbackfn A function that accepts up to three arguments. forEach calls the callbackfn function one time for each element in the array.
* @param thisArg An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value.
*/
forEach(callbackfn: (value: T, index: number, array: T[]) => void, thisArg?: any): void;
/**
* Calls a defined callback function on each element of an array, and returns an array that contains the results.
* @param callbackfn A function that accepts up to three arguments. The map method calls the callbackfn function one time for each element in the array.
* @param thisArg An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value.
*/
map<U>(callbackfn: (value: T, index: number, array: T[]) => U, thisArg?: any): U[];
/**
* Returns the elements of an array that meet the condition specified in a callback function.
* @param predicate A function that accepts up to three arguments. The filter method calls the predicate function one time for each element in the array.
* @param thisArg An object to which the this keyword can refer in the predicate function. If thisArg is omitted, undefined is used as the this value.
*/
filter<S extends T>(predicate: (value: T, index: number, array: T[]) => value is S, thisArg?: any): S[];
/**
* Returns the elements of an array that meet the condition specified in a callback function.
* @param predicate A function that accepts up to three arguments. The filter method calls the predicate function one time for each element in the array.
* @param thisArg An object to which the this keyword can refer in the predicate function. If thisArg is omitted, undefined is used as the this value.
*/
filter(predicate: (value: T, index: number, array: T[]) => unknown, thisArg?: any): T[];
/**
* Calls the specified callback function for all the elements in an array. The return value of the callback function is the accumulated result, and is provided as an argument in the next call to the callback function.
* @param callbackfn A function that accepts up to four arguments. The reduce method calls the callbackfn function one time for each element in the array.
* @param initialValue If initialValue is specified, it is used as the initial value to start the accumulation. The first call to the callbackfn function provides this value as an argument instead of an array value.
*/
reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T): T;
reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T, initialValue: T): T;
/**
* Calls the specified callback function for all the elements in an array. The return value of the callback function is the accumulated result, and is provided as an argument in the next call to the callback function.
* @param callbackfn A function that accepts up to four arguments. The reduce method calls the callbackfn function one time for each element in the array.
* @param initialValue If initialValue is specified, it is used as the initial value to start the accumulation. The first call to the callbackfn function provides this value as an argument instead of an array value.
*/
reduce<U>(callbackfn: (previousValue: U, currentValue: T, currentIndex: number, array: T[]) => U, initialValue: U): U;
/**
* Calls the specified callback function for all the elements in an array, in descending order. The return value of the callback function is the accumulated result, and is provided as an argument in the next call to the callback function.
* @param callbackfn A function that accepts up to four arguments. The reduceRight method calls the callbackfn function one time for each element in the array.
* @param initialValue If initialValue is specified, it is used as the initial value to start the accumulation. The first call to the callbackfn function provides this value as an argument instead of an array value.
*/
reduceRight(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T): T;
reduceRight(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T, initialValue: T): T;
/**
* Calls the specified callback function for all the elements in an array, in descending order. The return value of the callback function is the accumulated result, and is provided as an argument in the next call to the callback function.
* @param callbackfn A function that accepts up to four arguments. The reduceRight method calls the callbackfn function one time for each element in the array.
* @param initialValue If initialValue is specified, it is used as the initial value to start the accumulation. The first call to the callbackfn function provides this value as an argument instead of an array value.
*/
reduceRight<U>(callbackfn: (previousValue: U, currentValue: T, currentIndex: number, array: T[]) => U, initialValue: U): U;
[n: number]: T;
}
reactNamespace(react命名空间)
eactNamespace 配置项已不再推荐使用,请改用 jsxFactory。此配置原本用于指定在面向 TSX 文件(TypeScript 扩展的 JSX 文件)编译时调用的 React 类型对象,对应于 React.createElement 方法所在的命名空间。
由于推荐的做法是直接使用 jsxFactory 配置项来指定 JSX 工厂函数,下面展示如何使用 jsxFactory 来替代 reactNamespace 的示例:
tsconfig.json 示例:
{
"compilerOptions": {
"jsx": "react",
"jsxFactory": "React.createElement"
}
}
在上述配置中,jsx 设置为 "react" 表示使用 React 风格的 JSX,而 jsxFactory 设置为 "React.createElement" 明确指定 React 命名空间下的 createElement 方法作为 JSX 工厂函数。
TSX 文件示例:
import React from 'react';
function MyComponent() {
return <div>Hello, world!</div>;
}
在这个 TSX 文件中,虽然没有直接体现 jsxFactory 的使用,但通过配置它确保了 <div>Hello, world!</div>
这样的 JSX 语法会被编译为 React.createElement('div', null, 'Hello, world!') 的形式。
target
现代浏览器支持所有ES6特性,因此选择 ES6 作为目标通常是个不错的选择。你可能会根据代码部署的环境选择更低的目标,如果要兼容老版本环境;或者如果确定代码只在较新环境下运行,可以选择更高的目标。
target 设置决定了哪些 JavaScript 特性会被降级转译(downlevel),哪些会保持原样。例如,箭头函数在目标为 ES5 或更低时,会被转换为等效的普通函数表达式。
改变 target 同时也会影响到默认的 lib 值。你可以根据需要自由“混合搭配”target 和 lib 的设定,但为了方便,通常只需设置 target 即可自动匹配相应的 lib 配置。
特殊的值 ESNext 指代你当前使用的 TypeScript 版本所支持的最高 ES 版本。使用这个设置需谨慎,因为在不同的 TypeScript 版本间它所代表的含义可能不同,可能会使升级过程变得不可预测。
目前可选的值:
- es3
- es5
- es6/es2015
- es2016
- es2017
- es2018
- es2019
- es2020
- es2021
- es2022
- esnext
useDefineForClassFields
目前可忽略该配置项
useDefineForClassFields 此标志是用于迁移到即将发布的类字段标准版本的一部分。TypeScript 在 TC39(ECMAScript 技术委员会第39工作组)正式批准之前多年就已经引入了类字段的概念。即将发布的规范版本在运行时行为上与TypeScript的实现有所不同,但保留了相同的语法。
此标志用于切换到即将推出的ECMAScript运行时行为。
关于这一过渡的更多信息,你可以在 TypeScript 3.7 发布说明文档中阅读。
示例,在 tsconfig.json 文件中,你可以这样设置 useDefineForClassFields:
{
"compilerOptions": {
"target": "es2022",
"useDefineForClassFields": true
}
}
在这个示例中,我们将 target 设定为 es2022 或更高版本(根据 useDefineForClassFields 生效所需的最低目标版本调整),并启用 useDefineForClassFields,这表示TypeScript 编译器将按照即将来临的 ECMA Script 规范版本处理类字段的运行时行为。
例如,考虑以下 TypeScript 类:
class MyClass {
myField = 'initialValue';
constructor() {
console.log(this.myField); // 在不同行为下,初始化时机可能不同
}
}
当启用了 useDefineForClassFields,类字段的初始化时机和行为将遵循即将到来的ECMAScript标准,这可能与TypeScript早期版本的默认行为有所不同。
Projects
composite
composite 选项施加了一些约束条件,使得构建工具(包括 TypeScript 自身在 --build 模式下)能够快速判断一个项目是否已经构建过。
当开启此设置时:
- 如果未明确设置 rootDir,其默认值将被设定为包含 tsconfig.json 文件的目录。
- 所有实现文件必须被 include 模式匹配到或直接列在 files 数组中。如果违反了这个约束,tsc 编译器会告诉你哪些文件没有被指定。
- declaration 默认设置为 true,意味着会自动生成声明文件(.d.ts)。
示例,在 tsconfig.json 文件中:
{
"compilerOptions": {
"composite": true,
"declaration": true, // 这一项由于 composite 为 true 已经默认开启,所以这里可以省略或保持以作显式声明
"target": "es2017",
"module": "commonjs",
"outDir": "dist"
},
"include": [
"src/**/*"
]
}
在这个示例中,composite 被设为 true,意味着我们正配置项目支持作为复合项目的一部分。include 指令确保了 src 目录下的所有文件都会被编译。由于开启了 composite,declaration 会被默认开启,所以无需显式声明(这里依然列出是为了清晰)。这个配置告诉 TypeScript 编译器,所有源代码位于 src 目录下,并且输出的 JavaScript 文件和声明文件将放置在 dist 目录中。
disableReferencedProjectLoad(禁止自动加载项目引用到内存)
在多项目类型的 TypeScript 程序中,TypeScript 为了给诸如“查找所有引用”这类需要完整知识图谱的编辑器响应提供准确的结果,会将所有可用的项目加载到内存中。
如果你的项目非常大,可以使用 disableReferencedProjectLoad 配置项来禁止自动加载所有项目。这样一来,项目只会在你通过编辑器打开相关文件时动态地按需加载。
在 tsconfig.json 文件中:
{
"compilerOptions": {
// 其他编译选项...
},
"disableReferencedProjectLoad": true
}
在这个示例中,通过设置 "disableReferencedProjectLoad": true,告知 TypeScript 在编辑器环境中不要预先加载所有引用的项目。这有助于减少大型项目在启动和运行时的内存占用,提高编辑器响应速度,尤其是在初次加载或资源有限的环境下。当开发者实际访问或编辑某个具体文件时,对应的项目才会被加载。
disableSolutionSearching
在使用组合(composite)TypeScript项目时,此配置项提供了一种方式来声明你不想让项目在使用诸如“查找所有引用”或编辑器中的“跳转到定义”等功能时被包含进来。
此标志可用于提升大型组合项目的响应速度。
tsconfig.json 文件中:
{
"compilerOptions": {
// ...其他编译器选项
},
"disableSolutionSearching": true
}
在这个示例中,通过设置 "disableSolutionSearching": true,你指示TypeScript在使用编辑器的特定功能(如“查找所有引用”或“跳转到定义”)时,不要搜索并包含当前项目。这对于大型项目特别有用,因为它减少了编辑器在执行这些操作时需处理的项目数量,从而可能提高了编辑器的响应速度和整体性能。
disableSourceOfProjectReferenceRedirect(禁用源项目引用重定向)
略
incremental
略
tsBuildInfoFile(TS 构建信息文件)
略
Completeness(完整性)
skipDefaultLibCheck(跳过默认库检查)
请改用 skipLibCheck。跳过默认库声明文件的类型检查。
skipLibCheck(跳过库检查)
工程初始化默认开启了此配置项。
此配置项可以在牺牲类型系统准确性的情况下节省编译时间。例如,两个库可能以不一致的方式定义了相同的接口或类型。TypeScript不会全面检查所有文件,而是仅仅检查应用程序源代码中明确引用到的代码,从而提高编译速度。
常见应用场景:
当你考虑使用 skipLibCheck 时,一个典型的场景是在你的 node_modules 目录中有两个库的类型声明文件副本,它们可能是由于间接依赖或版本冲突导致的。在这种情况下,你应该优先考虑使用像Yarn的resolutions特性来确保依赖树中只有一个该依赖的副本,或者深入理解依赖解析机制以手动解决冲突,从而避免不必要的工具配置。
迁移过程中的应用:
在从一个TypeScript版本迁移到另一个版本时,如果新版本的变更导致了node_modules中依赖项或JavaScript标准库的类型错误,而你暂时不想在升级过程中处理这些问题,也可以临时启用 skipLibCheck。
注意:
如果遇到的问题来源于TypeScript的标准库,自TypeScript 4.5起,你可以利用库替换技术来替代标准库,从而针对性地解决问题,而无需全局禁用库文件的类型检查。
tsconfig.json 文件配置:
{
"compilerOptions": {
// ...其他编译器选项
"skipLibCheck": true
}
}
在上述示例中,通过设置 "skipLibCheck": true,你告诉TypeScript在编译过程中跳过对所有声明文件(.d.ts)的类型检查,仅检查你的源代码直接引用的部分,以此加速编译过程。但请记住,这可能会隐藏由外部库或标准库引起的类型错误,因此建议仅在理解其后果后谨慎使用。