Skip to content
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待

互操作约束

allowSyntheticDefaultImports

当设置为 true 时,allowSyntheticDefaultImports 允许你编写如下导入:

ts
import React from "react";

而不是:

ts
import * as React from "react";

当模块没有显式指定默认导出时。

例如,如果没有将 allowSyntheticDefaultImports 设为 true:

ts
// @filename: utilFunctions.js
const 
getStringLength
= (
str
) =>
str
.length;
module
.
exports
= {
getStringLength
,
}; // @filename: index.ts import utils from "./utilFunctions";
Module '"/home/runner/work/ts-website/ts-website/utilFunctions"' has no default export.
const
count
=
utils
.getStringLength("Check JS");
Try

这段代码会引发错误,因为没有可以导入的 default 对象。尽管感觉上应该可以。 为了方便起见,像 Babel 这样的转译器会在没有创建默认导出时自动创建一个。使模块看起来更像:

js
// @filename: utilFunctions.js
const getStringLength = (str) => str.length;
const allFunctions = {
  getStringLength,
};

module.exports = allFunctions;
module.exports.default = allFunctions;

此标志不影响 TypeScript 输出的 JavaScript,仅用于类型检查。 此选项使 TypeScript 的行为与 Babel 保持一致,Babel 会输出额外的代码,使使用模块的默认导出更加符合人体工程学。

erasableSyntaxOnly

Node.js 从 v23.6 开始支持直接运行 TypeScript 文件; 然而,在此模式下仅支持没有运行时语义的 TypeScript 特定语法。 换句话说,必须能够轻松地从文件中擦除任何 TypeScript 特定语法,留下一个有效的 JavaScript 文件。

这意味着不支持以下结构:

  • enum 声明
  • 包含运行时代码的 namespacemodule
  • 类中的参数属性
  • 非 ECMAScript 的 import =export = 赋值
  • <prefix> 风格的类型断言
ts
// ❌ 错误:一个 `import ... = require(...)` 别名
import foo = require("foo");

// ❌ 错误:一个包含运行时代码的命名空间。
namespace container {
    foo.method();

    export type Bar = string;
}

// ❌ 错误:一个 `import =` 别名
import Bar = container.Bar;

class Point {
    // ❌ 错误:参数属性
    constructor(public x: number, public y: number) { }
}

// ❌ 错误:一个 `export =` 赋值。
export = Point;

// ❌ 错误:一个 enum 声明。
enum Direction {
    Up,
    Down,
    Left,
    Right,
}

// ❌ 错误:<prefix> 风格的类型断言。
const num = <number>1;

类似的工具如 ts-blank-spaceAmaro(Node.js 中类型剥离的底层库)也有相同的限制。 这些工具在遇到不符合这些要求的代码时会提供有用的错误消息,但你仍然只有在你实际尝试运行代码时才会发现代码不起作用。

--erasableSyntaxOnly 标志将使 TypeScript 对大多数具有运行时行为的 TypeScript 特定结构报错。

ts
class C {
    constructor(public x: number) { }
    //          ~~~~~~~~~~~~~~~~
    // 错误!当启用 'erasableSyntaxOnly' 时,不允许使用此语法。
    }
}

通常,你会希望将此标志与 --verbatimModuleSyntax 结合使用,后者确保模块包含适当的导入语法,并且不会发生导入省略。

esModuleInterop 推荐

默认情况下(esModuleInterop 为 false 或未设置),TypeScript 将 CommonJS/AMD/UMD 模块视为类似于 ES6 模块。这样做时,有两个方面被证明是错误的假设:

  • 命名空间导入如 import * as moment from "moment" 的行为与 const moment = require("moment") 相同

  • 默认导入如 import moment from "moment" 的行为与 const moment = require("moment").default 相同

这种不匹配导致了两个问题:

  • ES6 模块规范规定命名空间导入 (import * as x) 只能是一个对象,而 TypeScript 将其视为与 = require("x") 相同,从而允许将导入视为函数并可调用。根据规范,这是无效的。

  • 虽然符合 ES6 模块规范,但大多数具有 CommonJS/AMD/UMD 模块的库并不像 TypeScript 的实现那样严格。

启用 esModuleInterop 将修复 TypeScript 转译代码中的这两个问题。第一个改变了编译器的行为,第二个通过两个新的辅助函数修复,它们提供了一个垫片以确保在输出的 JavaScript 中的兼容性:

ts
import * as fs from "fs";
import _ from "lodash";

fs.readFileSync("file.txt", "utf8");
_.chunk(["a", "b", "c", "d"], 2);

禁用 esModuleInterop 时:

ts
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const fs = require("fs");
const lodash_1 = require("lodash");
fs.readFileSync("file.txt", "utf8");
lodash_1.default.chunk(["a", "b", "c", "d"], 2);
Try

esModuleInterop 设置为 true 时:

ts
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const fs = __importStar(require("fs"));
const lodash_1 = __importDefault(require("lodash"));
fs.readFileSync("file.txt", "utf8");
lodash_1.default.chunk(["a", "b", "c", "d"], 2);
Try

注意:命名空间导入 import * as fs from "fs" 仅计入导入对象上拥有的属性(基本上是在对象上设置而不是通过原型链继承的属性)。如果你导入的模块使用继承属性定义其 API,你需要使用默认导入形式(import fs from "fs"),或禁用 esModuleInterop

注意:你可以通过启用 importHelpers 使 JS 输出更简洁:

ts
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const fs = tslib_1.__importStar(require("fs"));
const lodash_1 = tslib_1.__importDefault(require("lodash"));
fs.readFileSync("file.txt", "utf8");
lodash_1.default.chunk(["a", "b", "c", "d"], 2);
Try

启用 esModuleInterop 也将启用 allowSyntheticDefaultImports

forceConsistentCasingInFileNames 推荐

  • 默认值: true

  • 发布版本: 1.8

TypeScript 遵循其运行所在文件系统的大小写敏感规则。 如果一些开发人员在大小写敏感的文件系统上工作而其他人不是,这可能会出现问题。 如果一个文件试图通过指定 ./FileManager.ts 来导入 fileManager.ts,那么在不区分大小写的文件系统上会找到该文件,但在区分大小写的文件系统上则不会。

当设置此选项时,如果程序尝试以与磁盘上不同的大小写包含文件,TypeScript 将发出错误。

isolatedDeclarations

  • 发布版本: 5.5

要求导出有足够的注解,以便其他工具可以轻松生成声明文件。

有关更多信息,请参阅 5.5 发布说明

isolatedModules

虽然你可以使用 TypeScript 从 TypeScript 代码生成 JavaScript 代码,但使用其他转译器(如 Babel)来执行此操作也很常见。 然而,其他转译器一次只操作一个文件,这意味着它们无法应用依赖于理解完整类型系统的代码转换。 此限制也适用于 TypeScript 的 ts.transpileModule API,一些构建工具使用该 API。

这些限制可能导致某些 TypeScript 特性(如 const enumnamespace)出现运行时问题。 设置 isolatedModules 标志会告诉 TypeScript,如果你编写了某些无法被单文件转译过程正确解释的代码,它会发出警告。

它不会改变你的代码的行为,也不会改变 TypeScript 检查和输出过程的行为。

当启用 isolatedModules 时,一些不起作用的代码示例。

非值标识符的导出

在 TypeScript 中,你可以导入一个类型,然后随后导出它:

ts
import { 
someType
,
someFunction
} from "someModule";
someFunction
();
export {
someType
,
someFunction
};
Try

因为 someType 没有值,输出的 export 不会尝试导出它(这在 JavaScript 中将是运行时错误):

js
export { someFunction };

单文件转译器不知道 someType 是否产生值,因此导出一个仅引用类型的名称是错误的。

非模块文件

如果设置了 isolatedModules,则命名空间只允许在模块中(这意味着它具有某种形式的 import/export)。如果在非模块文件中找到命名空间,则会发生错误:

ts
namespace Instantiated {
Namespaces are not allowed in global script files when 'isolatedModules' is enabled. If this file is not intended to be a global script, set 'moduleDetection' to 'force' or add an empty 'export {}' statement.
export const
x
= 1;
}
Try

此限制不适用于 .d.ts 文件。

const enum 成员的引用

在 TypeScript 中,当你引用 const enum 成员时,该引用在输出的 JavaScript 中会被其实际值替换。将此 TypeScript:

ts
declare const enum 
Numbers
{
Zero
= 0,
One
= 1,
}
console
.
log
(
Numbers
.
Zero
+
Numbers
.
One
);
Try

转换为以下 JavaScript:

ts
"use strict";
console.log(0 + 1);
Try

如果没有这些成员值的知识,其他转译器无法替换对 Numbers 的引用,如果保留原样,这将是运行时错误(因为运行时没有 Numbers 对象)。 因此,当设置 isolatedModules 时,引用环境 const enum 成员是错误的。

  • 发布版本: 2.5

这反映了 Node.js 中的相同标志;它不会解析符号链接的真实路径。

此标志还表现出与 Webpack 的 resolve.symlinks 选项相反的行为(即,将 TypeScript 的 preserveSymlinks 设置为 true 相当于将 Webpack 的 resolve.symlinks 设置为 false,反之亦然)。

启用此标志后,对模块和包的引用(例如 import/// <reference type="..." /> 指令)都相对于符号链接文件的位置解析,而不是相对于符号链接解析到的路径。

verbatimModuleSyntax

  • 发布版本: 5.0

默认情况下,TypeScript 会执行所谓的导入省略。 基本上,如果你编写类似

ts
import { Car } from "./car";

export function drive(car: Car) {
    // ...
}

TypeScript 检测到你仅将导入用于类型,并完全删除该导入。 你的输出 JavaScript 可能看起来像这样:

js
export function drive(car) {
    // ...
}

大多数情况下这很好,因为如果 Car 不是从 ./car 导出的值,我们会得到运行时错误。

但它确实为某些边缘情况增加了一层复杂性。 例如,注意没有像 import "./car"; 这样的语句——导入被完全删除了。 这对于有副作用或没有副作用的模块确实有影响。

TypeScript 的 JavaScript 输出策略还有另外几层复杂性——导入省略并不总是仅由导入的使用方式驱动——它通常还会查看值是如何声明的。 因此,并不总是清楚像下面这样的代码

ts
export { Car } from "./car";

是否应该保留或删除。 如果 Car 是用 class 之类的东西声明的,那么它可以保留在生成的 JavaScript 文件中。 但如果 Car 仅声明为 type 别名或 interface,那么 JavaScript 文件根本不应该导出 Car

虽然 TypeScript 可能能够根据跨文件的信息做出这些输出决定,但并非每个编译器都能做到。

导入和导出上的 type 修饰符在这些情况下有一点帮助。 我们可以通过使用 type 修饰符明确导入或导出是否仅用于类型分析,并且可以在 JavaScript 文件中完全删除。

ts
// 这个语句可以在 JS 输出中完全删除
import type * as car from "./car";

// 命名导入/导出 'Car' 可以在 JS 输出中删除
import { type Car } from "./car";
export { type Car } from "./car";

type 修饰符本身并不十分有用——默认情况下,模块省略仍然会删除导入,并且没有什么强制你区分 type 和普通的导入和导出。 因此 TypeScript 有标志 --importsNotUsedAsValues 来确保你使用 type 修饰符,--preserveValueImports 来阻止某些模块省略行为,以及 --isolatedModules 来确保你的 TypeScript 代码在不同编译器之间工作。 不幸的是,理解这三个标志的细节很难,并且仍然存在一些具有意外行为的边缘情况。

TypeScript 5.0 引入了一个名为 --verbatimModuleSyntax 的新选项来简化情况。 规则更简单——任何没有 type 修饰符的导入或导出都会保留。 任何使用 type 修饰符的内容都会被完全删除。

ts
// 完全擦除。
import type { A } from "a";

// 重写为 'import { b } from "bcd";'
import { b, type c, type d } from "bcd";

// 重写为 'import {} from "xyz";'
import { type xyz } from "xyz";

有了这个新选项,所见即所得。

然而,在模块互操作方面,这确实有一些影响。 在此标志下,当你的设置或文件扩展名暗示了不同的模块系统时,ECMAScript importexport 将不会被重写为 require 调用。 相反,你会得到一个错误。 如果你需要输出使用 requiremodule.exports 的代码,你必须使用早于 ES2015 的 TypeScript 模块语法:

输入 TypeScript输出 JavaScript
ts
import foo = require("foo");
js
const foo = require("foo");
ts
function foo() {}
function bar() {}
function baz() {}

export = {
    foo,
    bar,
    baz
};
js
function foo() {}
function bar() {}
function baz() {}

module.exports = {
    foo,
    bar,
    baz
};

虽然这是一个限制,但它确实有助于使一些问题更加明显。 例如,在 --module node16 下,很容易忘记设置 package.json 中的 type 字段。 结果,开发人员会开始编写 CommonJS 模块而不是 ES 模块却没有意识到,导致令人惊讶的查找规则和 JavaScript 输出。 这个新标志确保你明确你正在使用的文件类型,因为语法有意不同。

由于 --verbatimModuleSyntax--importsNotUsedAsValues--preserveValueImports 提供了更一致的故事,这两个现有标志将被弃用,转而支持它。

有关更多详细信息,请阅读原始拉取请求其提案问题