4499 words
22 minutes
TSConfig 模块配置
2025-02-26 14:09:27
2025-02-27 15:33:13

← 返回 TSConfig 参考指南


允许任意扩展名 - allowArbitraryExtensions#

在 TypeScript 5.0 中,当导入路径以一个非标准的 JavaScript 或 TypeScript 文件扩展名结尾时,编译器会寻找该路径对应的声明文件,格式为 {文件基础名}.d.{扩展名}.ts。例如,如果你在打包项目中使用 CSS 加载器,你可能想要为这些样式表编写(或生成)声明文件:

/* app.css */ .cookie-banner { display: none; }
// app.d.css.ts declare const css: { cookieBanner: string; }; export default css; // App.tsx import styles from "./app.css"; styles.cookieBanner; // string

默认情况下,这种导入会触发一个错误,提示你 TypeScript 不理解这种文件类型,并且你的运行时可能不支持导入它。但如果你已经配置了运行时或打包工具来处理它,你可以使用新的 —allowArbitraryExtensions 编译器选项来抑制这个错误。

注意,历史上,通常可以通过添加一个名为 app.css.d.ts 而不是 app.d.css.ts 的声明文件来达到类似的效果 - 但这仅仅是通过 Node 的 CommonJS require 解析规则来实现的。严格来说,前者被解释为 app.css.js 这个 JavaScript 文件的声明文件。由于在 Node 的 ESM 支持中相对文件导入需要包含扩展名,在 —moduleResolution node16 或 nodenext 下,TypeScript 会对我们的示例报错。

更多信息,请查看此功能的提案和相应的拉取请求。

发布版本:5.0

允许导入 TS 扩展名 - allowImportingTsExtensions#

—allowImportingTsExtensions 允许 TypeScript 文件之间使用 TypeScript 特定的扩展名(如 .ts、.mts 或 .tsx)进行导入。

此标志仅在启用 —noEmit 或 —emitDeclarationOnly 时允许使用,因为这些导入路径在 JavaScript 输出文件中将无法解析。这里的期望是你的解析器(例如打包工具、运行时或其他工具)将使这些 .ts 文件之间的导入正常工作。

发布版本:5.0

允许 UMD 全局访问 - allowUmdGlobalAccess#

当设置为 true 时,allowUmdGlobalAccess 允许你从模块文件内部将 UMD 导出作为全局变量访问。模块文件是指具有导入和/或导出的文件。没有这个标志,使用 UMD 模块的导出需要一个导入声明。

这个标志的一个用例是 Web 项目,你知道特定的库(如 jQuery 或 Lodash)在运行时总是可用的,但你无法通过导入来访问它。

发布版本:3.5

基础 URL - baseUrl#

设置一个基础目录,用于解析裸说明符模块名称。例如,在以下目录结构中:

project ├── ex.ts ├── hello │ └── world.ts └── tsconfig.json

使用 “baseUrl”: ”./“,TypeScript 将从 tsconfig.json 所在的文件夹开始查找文件:

import { helloWorld } from "hello/world"; console.log(helloWorld);

这种解析方式的优先级高于从 node_modules 中查找。

这个功能最初是为了在浏览器中与 AMD 模块加载器一起使用而设计的,不建议在其他上下文中使用。从 TypeScript 4.1 开始,使用 paths 时不再需要设置 baseUrl。

发布版本:2.0

自定义条件 - customConditions#

—customConditions 接受一个额外条件列表,当 TypeScript 从 package.json 的 exports 或 imports 字段进行解析时,这些条件应该被满足。这些条件会被添加到解析器默认使用的现有条件中。

例如,当在 tsconfig.json 中这样设置该字段时:

{ "compilerOptions": { "target": "es2022", "moduleResolution": "bundler", "customConditions": ["my-condition"] } }

任何时候引用 package.json 中的 exports 或 imports 字段时,TypeScript 都会考虑名为 my-condition 的条件。

所以当从具有以下 package.json 的包中导入时:

{ // ... "exports": { ".": { "my-condition": "./foo.mjs", "node": "./bar.mjs", "import": "./baz.mjs", "require": "./biz.mjs" } } }

TypeScript 将尝试查找对应于 foo.mjs 的文件。

此字段仅在 —moduleResolution 选项设置为 node16、nodenext 和 bundler 时有效。

相关配置:

  • moduleResolution
  • resolvePackageJsonExports
  • resolvePackageJsonImports

发布版本:5.0

模块系统 - module#

设置程序的模块系统。更多信息请参阅 TypeScript 的模块选项背后的理论及其参考页面。对于现代 Node.js 项目,你很可能需要使用 “nodenext”,对于将要打包的代码,则使用 preserve 或 esnext。

更改 module 会影响 moduleResolution,moduleResolution 也有一个参考页面。

以下是这个文件的一些示例输出:

// @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; });

AMD:

define(["require", "exports", "./constants"], function (require, exports, constants_1) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.twoPi = void 0; exports.twoPi = constants_1.valueOfPi * 2; });

System:

System.register(["./constants"], function (exports_1, context_1) { "use strict"; var constants_1, twoPi; var __moduleName = context_1 && context_1.id; return { setters: [ function (constants_1_1) { constants_1 = constants_1_1; } ], execute: function () { exports_1("twoPi", twoPi = constants_1.valueOfPi * 2); } }; });

ESNext:

import { valueOfPi } from "./constants"; export const twoPi = valueOfPi * 2;

ES2015/ES6/ES2020/ES2022:

import { valueOfPi } from "./constants"; export const twoPi = valueOfPi * 2;

除了 ES2015/ES6 的基本功能外,ES2020 增加了对动态导入和 import.meta 的支持,而 ES2022 进一步增加了对顶层 await 的支持。

node16/nodenext: 从 4.7+ 版本开始可用,node16 和 nodenext 模式与 Node 的原生 ECMAScript 模块支持集成。生成的 JavaScript 使用 CommonJS 或 ES2020 输出,具体取决于文件扩展名和最近的 package.json 中的 type 设置。模块解析也有所不同。你可以在手册和模块参考中了解更多信息。

preserve: 在 —module preserve(在 TypeScript 5.4 中添加)中,输入文件中编写的 ECMAScript 导入和导出在输出中保持不变,而 CommonJS 风格的 import x = require(”…”) 和 export = … 语句会被转换为 CommonJS require 和 module.exports。换句话说,每个单独的导入或导出语句的格式都会被保留,而不是被强制转换为整个编译(甚至整个文件)的单一格式。

import { valueOfPi } from "./constants"; const constants = require("./constants"); export const piSquared = valueOfPi * constants.valueOfPi;

虽然在同一个文件中混合使用导入和 require 调用的情况很少见,但这种模块模式最能反映大多数现代打包工具以及 Bun 运行时的功能。

为什么在使用打包工具或 Bun 时还要关心 TypeScript 的模块生成(你可能也设置了 noEmit)?TypeScript 的类型检查和模块解析行为会受到它将要生成的模块格式的影响。设置 module 可以让 TypeScript 了解你的打包工具或运行时将如何处理导入和导出,这确保了你在导入值上看到的类型准确反映了在运行时或打包后会发生的情况。

None:

"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.twoPi = void 0; const constants_1 = require("./constants"); exports.twoPi = constants_1.valueOfPi * 2;

默认值: 如果 target 是 ES5 则为 CommonJS;否则为 ES6/ES2015。

允许的值:

  • none
  • commonjs
  • amd
  • umd
  • system
  • es6/es2015
  • es2020
  • es2022
  • esnext
  • node16
  • nodenext
  • preserve

相关配置:

  • moduleResolution
  • esModuleInterop
  • allowImportingTsExtensions
  • allowArbitraryExtensions
  • resolveJsonModule

发布版本:1.0

模块解析 - moduleResolution#

指定模块解析策略:

  • ‘node16’ 或 ‘nodenext’ 用于现代版本的 Node.js。Node.js v12 及更高版本同时支持 ECMAScript 导入和 CommonJS require,它们使用不同的解析算法。这些 moduleResolution 值与相应的 module 值结合使用时,会根据 Node.js 在输出的 JavaScript 代码中看到的是 import 还是 require 来选择正确的算法。
  • ‘node10’(以前称为 ‘node’)用于早于 v10 的 Node.js 版本,这些版本仅支持 CommonJS require。在现代代码中,你可能不需要使用 node10。
  • ‘bundler’ 用于打包工具。与 node16 和 nodenext 一样,此模式支持 package.json 的 “imports” 和 “exports”,但与 Node.js 解析模式不同的是,bundler 在导入时从不要求相对路径上的文件扩展名。
  • ‘classic’ 在 TypeScript 1.6 发布之前使用。classic 不应该被使用。

有参考页面解释了 TypeScript 模块解析背后的理论和每个选项的详细信息。

默认值: 如果 module 是 AMD、UMD、System 或 ES6/ES2015,则为 Classic;如果 module 是 node16 或 nodenext,则匹配相应值;其他情况下为 Node。

允许的值:

  • classic
  • node10/node
  • node16
  • nodenext
  • bundler

相关配置:

  • module
  • paths
  • baseUrl
  • rootDirs
  • moduleSuffixes
  • customConditions
  • resolvePackageJsonExports
  • resolvePackageJsonImports

发布版本:1.6

模块后缀 - moduleSuffixes#

提供一种方式来覆盖默认的文件名后缀列表,用于解析模块。

{ "compilerOptions": { "moduleSuffixes": [".ios", ".native", ""] } }

给定上述配置,对于如下的导入:

import * as foo from "./foo";

TypeScript 将依次查找相对文件 ./foo.ios.ts、./foo.native.ts 和 ./foo.ts。

注意 moduleSuffixes 中的空字符串 "",这对于 TypeScript 也能查找 ./foo.ts 是必需的。

这个功能在 React Native 项目中特别有用,其中每个目标平台可以使用带有不同 moduleSuffixes 的单独 tsconfig.json。

发布版本:4.7

不解析 - noResolve#

默认情况下,TypeScript 会检查初始文件集中的 import 和 <reference 指令,并将这些解析的文件添加到你的程序中。

如果设置了 noResolve,这个过程就不会发生。但是,import 语句仍然会被检查以确保它们解析到一个有效的模块,所以你需要确保通过其他方式满足这一点。

发布版本:1.0

检查导入的副作用 - noUncheckedSideEffectImports#

在 JavaScript 中,可以导入一个模块而不实际导入其中的任何值。

import "some-module";

这些导入通常被称为副作用导入,因为它们唯一有用的行为是通过执行某些副作用(如注册全局变量或向原型添加 polyfill)来提供。

默认情况下,TypeScript 不会检查这些导入的有效性。如果导入解析到一个有效的源文件,TypeScript 将加载并检查该文件。如果找不到源文件,TypeScript 将静默忽略该导入。

这种行为很令人惊讶,但它部分源于对 JavaScript 生态系统中模式的建模。例如,这种语法也被用于打包工具中的特殊加载器来加载 CSS 或其他资源。你的打包工具可能配置成允许你通过编写如下代码来包含特定的 .css 文件:

import "./button-component.css"; export function Button() { // ... }

不过,这掩盖了副作用导入中的潜在拼写错误。

当启用 —noUncheckedSideEffectImports 时,如果 TypeScript 找不到副作用导入的源文件,就会报错。

import "oops-this-module-does-not-exist"; // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // error: 找不到模块 'oops-this-module-does-not-exist' 或其对应的类型声明。

启用此选项时,一些原本正常工作的代码可能会收到错误,就像上面的 CSS 示例。要解决这个问题,只想编写资源的副作用导入的用户可能最好使用通配符说明符编写所谓的环境模块声明。它会放在一个全局文件中,看起来像这样:

// ./src/globals.d.ts // 将所有 CSS 文件识别为模块导入 declare module "*.css" {}

事实上,你的项目中可能已经有这样的文件了!例如,运行 vite init 之类的命令可能会创建类似的 vite-env.d.ts。

发布版本:5.6

路径映射 - paths#

一系列用于重新映射导入路径的配置项,这些映射路径相对于 baseUrl(如果设置了的话),否则相对于 tsconfig 文件本身。在模块解析参考页面中有更详细的 paths 相关内容。

paths 让你可以声明 TypeScript 应该如何解析你的 require/imports 中的导入。

{ "compilerOptions": { "paths": { "jquery": ["./vendor/jquery/dist/jquery"] } } }

这样你就可以直接写 import "jquery",并在本地获得所有正确的类型定义。

{ "compilerOptions": { "paths": { "app/*": ["./src/app/*"], "config/*": ["./src/app/_config/*"], "environment/*": ["./src/environments/*"], "shared/*": ["./src/app/_shared/*"], "helpers/*": ["./src/helpers/*"], "tests/*": ["./src/tests/*"] } } }

在这种情况下,你可以告诉 TypeScript 文件解析器支持多个自定义前缀来查找代码。

注意,这个功能不会改变 tsc 如何生成导入路径,所以 paths 应该只用于告诉 TypeScript 另一个工具(如打包工具)在运行时或打包时会使用这个映射。

发布版本:2.0

解析 JSON 模块 - resolveJsonModule#

允许导入带有 .json 扩展名的模块,这在 Node.js 项目中是一种常见做法。这包括根据静态 JSON 结构生成导入的类型定义。

TypeScript 默认不支持解析 JSON 文件:

// @filename: settings.json { "repo": "TypeScript", "dry": false, "debug": false } // @filename: index.ts import settings from "./settings.json"; // 错误:找不到模块 './settings.json'。请考虑使用 '--resolveJsonModule' 来导入带有 '.json' 扩展名的模块。 settings.debug === true; settings.dry === 2;

启用此选项后,可以导入 JSON 并验证该 JSON 文件中的类型:

// @filename: settings.json { "repo": "TypeScript", "dry": false, "debug": false } // @filename: index.ts import settings from "./settings.json"; settings.debug === true; settings.dry === 2; // 错误:这个比较似乎是无意的,因为类型 'boolean' 和 'number' 没有重叠。

发布版本:2.9

解析 package.json 导出 - resolvePackageJsonExports#

—resolvePackageJsonExports 强制 TypeScript 在从 node_modules 中读取包时查看 package.json 文件的 exports 字段。

当 —moduleResolution 设置为 node16、nodenext 或 bundler 时,此选项默认为 true。

默认值: 当 moduleResolution 为 node16、nodenext 或 bundler 时为 true;否则为 false

相关配置:

  • moduleResolution
  • customConditions
  • resolvePackageJsonImports

发布版本:5.0

解析 package.json 导入 - resolvePackageJsonImports#

—resolvePackageJsonImports 强制 TypeScript 在执行以 # 开头的查找时,查看其祖先目录中包含的 package.json 文件的 imports 字段。

当 —moduleResolution 设置为 node16、nodenext 或 bundler 时,此选项默认为 true。

默认值: 当 moduleResolution 为 node16、nodenext 或 bundler 时为 true;否则为 false

相关配置:

  • moduleResolution
  • customConditions
  • resolvePackageJsonExports

发布版本:5.0

重写相对导入扩展名 - rewriteRelativeImportExtensions#

在输出文件中将相对导入路径中的 .ts、.tsx、.mts 和 .cts 文件扩展名重写为它们的 JavaScript 等效扩展名。

发布版本:5.7

根目录 - rootDir#

默认值:所有非声明输入文件的最长公共路径。如果设置了 composite,则默认为包含 tsconfig.json 文件的目录。

当 TypeScript 编译文件时,它会在输出目录中保持与输入目录相同的目录结构。

例如,假设你有以下输入文件:

MyProj ├── tsconfig.json ├── core ├── a.ts ├── b.ts ├── sub ├── c.ts ├── types.d.ts

rootDir 的推断值是所有非声明输入文件的最长公共路径,在这个例子中是 core/。

如果你的 outDir 是 dist,TypeScript 会生成这样的目录树:

MyProj ├── dist ├── a.js ├── b.js ├── sub ├── c.js

但是,你可能希望 core 成为输出目录结构的一部分。通过在 tsconfig.json 中设置 rootDir: ”.”,TypeScript 会生成这样的目录树:

MyProj ├── dist ├── core ├── a.js ├── b.js ├── sub ├── c.js

重要的是,rootDir 不会影响哪些文件成为编译的一部分。它与 tsconfig.json 中的 include、exclude 或 files 设置没有任何关联。

注意,TypeScript 永远不会将输出文件写入到 outDir 之外的目录,也永远不会跳过文件的生成。因此,rootDir 还强制要求所有需要生成的文件都必须在 rootDir 路径下。

例如,假设你有这样的目录树:

MyProj ├── tsconfig.json ├── core ├── a.ts ├── b.ts ├── helpers.ts

将 rootDir 指定为 core 并将 include 指定为 * 将会产生错误,因为它会创建一个需要在 outDir 之外生成的文件(即 ../helpers.js)。

默认值: 从输入文件列表中计算得出。

发布版本:1.5

根目录列表 - rootDirs#

使用 rootDirs,你可以告诉编译器有多个”虚拟”目录作为单个根目录。这允许编译器在这些”虚拟”目录中解析相对模块导入,就像它们被合并到一个目录中一样。

例如:

src └── views └── view1.ts (可以导入 "./template1", "./view2") └── view2.ts (可以导入 "./template1", "./view1") generated └── templates └── views └── template1.ts (可以导入 "./view1", "./view2")
{ "compilerOptions": { "rootDirs": ["src/views", "generated/templates/views"] } }

这不会影响 TypeScript 如何生成 JavaScript,它只是模拟了这些文件在运行时可以通过这些相对路径工作的假设。

rootDirs 可以通过在另一个文件夹中为生成的 .d.ts 文件提供一个位置,来为非 TypeScript 或 JavaScript 文件提供一个单独的”类型层”。这种技术对于使用导入非代码文件的打包应用程序特别有用:

src └── index.ts └── css └── main.css └── navigation.css generated └── css └── main.css.d.ts └── navigation.css.d.ts
{ "compilerOptions": { "rootDirs": ["src", "generated"] } }

这种技术允许你为非代码源文件提前生成类型。导入操作会基于源文件的位置自然工作。例如,./src/index.ts 可以导入文件 ./src/css/main.css,而 TypeScript 会通过相应的生成声明文件了解打包工具对该文件类型的处理行为。

// @filename: index.ts import { appClass } from "./main.css";

默认值: 从输入文件列表中计算得出。

发布版本:2.0

Type Roots - typeRoots#

默认情况下,所有可见的 “@types” 包都会被包含在编译过程中。在任何封闭文件夹的 node_modules/@types 中的包都被视为可见。例如,这意味着 ./node_modules/@types/、../node_modules/@types/、../../node_modules/@types/ 等目录下的包都会被包含。

如果指定了 typeRoots,则只有 typeRoots 下的包会被包含。例如:

{ "compilerOptions": { "typeRoots": ["./typings", "./vendor/types"] } }

这个配置文件将只包含 ./typings 和 ./vendor/types 下的包,而不会包含 ./node_modules/@types 中的包。所有路径都是相对于 tsconfig.json 的。

相关配置:

  • types

发布版本: 2.0

Types - 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");

moment 的导入仍然会是完全类型化的。

当你设置了这个选项时,如果没有在 types 数组中包含某个模块,它将:

  • 不会向你的项目添加全局变量(例如 node 中的 process 或 Jest 中的 expect)
  • 不会让导出出现在自动导入建议中

这个特性与 typeRoots 的不同之处在于,它是关于指定你想要包含的确切类型,而 typeRoots 则是支持指定你想要的特定文件夹。

相关配置:

  • typeRoots

发布版本: 2.0

TSConfig 模块配置
https://0bipinnata0.my/posts/typescript/tsconfig/02-modules/
Author
0bipinnata0
Published at
2025-02-26 14:09:27