理解 Typescript 配置檔案

mingzhong發表於2019-02-16

熟悉 Typescript 配置選項是 TS 專案開發的最基本要求。
TS 使用 tsconfig.json 作為其配置檔案,它主要包含兩塊內容:

  1. 指定待編譯的檔案
  2. 定義編譯選項

另外,一般來說,tsconfig.json 檔案所處的路徑就是當前 TS 專案的根路徑

基本用法

TS 的編譯命令為 tsc ,當我們在命令列中直接輸入 tsc 時,會列印出如下的使用說明:

$ tsc
Version 2.7.2
Syntax:   tsc [options] [file ...]
Examples: tsc hello.ts
          tsc --outFile file.js file.ts
          tsc @args.txt

Options:
-h, --help                                         Print this message.
--all                                              Show all compiler options.
-v, --version                                      Print the compiler`s version.
...

如果僅僅是編譯少量的檔案,我們可以直接使用 tsc ,通過其選項來設定編譯配置,如:

tsc --outFile file.js --target es3 --module commonjs file.ts

但如果是編譯整個專案的話,最推薦的做法是使用 tsconfig.json 檔案,這樣就不用每次編譯時都還得手動敲配置,而且也便於團隊協作。

以下是讓 tsc 使用 tsconfig.json 的兩種方式:

  • 不顯式指定 tsconfig.json ,此時,編譯器會從當前路徑開始尋找 tsconfig.json 檔案,如果沒有找到,則繼續往上級路徑逐步尋找,直到找到為止
  • 通過 --project (或縮寫 -p )指定一個包含 tsconfig.json 的路徑,或者包含配置資訊的 .json 檔案路徑

注意,tsc 的命令列選項具有優先順序,會覆蓋 tsconfig.json 中的同名選項。

使用示例

下面是一個簡單的配置示例:

{
  "compilerOptions": {
    "module": "commonjs",
    "noImplicitAny": true,
    "removeComments": true,
    "preserveConstEnums": true,
    "sourceMap": true
  },
  "files": [
    "app.ts",
    "foo.ts",
  ]
}

其中,compilerOptions 用來配置編譯選項,files 用來指定待編譯檔案。
這裡的待編譯檔案是指入口檔案,任何被入口檔案依賴的檔案,比如 foo.ts 依賴 bar.ts ,那這裡並不需要寫上 bar.ts ,編譯器會自動把所有的依賴檔案納為編譯物件。

也可以使用 includeexclude 來指定和排除待編譯檔案:

{
  "compilerOptions": {
    "module": "commonjs",
    "noImplicitAny": true,
    "removeComments": true,
    "preserveConstEnums": true,
    "sourceMap": true
  },
  "include": [
    "src/**/*"
  ],
  "exclude": [
    "node_modules",
    "**/*.spec.ts"
  ]
}

所以,總結一下,指定待編譯檔案有兩種方式:

  • 使用 files 屬性
  • 使用 includeexclude 屬性

開發者可以按照自己的喜好使用其中任意一種。但它們不是互斥的,在某些情況下兩者搭配起來使用效果更佳。

配置說明

檔案指定

files 屬性是一個陣列,陣列元素可以是相對檔案路徑和絕對檔案路徑。

includeexclude 屬性也是一個陣列,但陣列元素是類似 glob 的檔案模式。它支援的 glob 萬用字元包括:

  • * :匹配 0 或多個字元(注意:不含路徑分隔符)
  • ? :匹配任意單個字元(注意:不含路徑分隔符)
  • **/ :遞迴匹配任何子路徑

在繼續說明之前,有必要先了解下在編譯器眼裡什麼樣的檔案才算是 TS 檔案
TS 檔案指擴充名為 .ts.tsx.d.ts 的檔案。如果開啟了 allowJs 選項,那 .js.jsx 檔案也屬於 TS 檔案。

如果僅僅包含一個 * 或者 .* ,那麼只有TS 檔案才會被包含。

如果 filesinclude 都未設定,那麼除了 exclude 排除的檔案,編譯器會預設包含路徑下的所有 TS 檔案

如果同時設定 filesinclude ,那麼編譯器會把兩者指定的檔案都引入。

如果未設定 exclude ,那其預設值為 node_modulesbower_componentsjspm_packages 和編譯選項 outDir 指定的路徑。

exclude 只對 include 有效,對 files 無效。即 files 指定的檔案如果同時被 exclude 排除,那麼該檔案仍然會被編譯器引入。

前面提到,任何被 filesinclude 引入的檔案的依賴會被自動引入。
反過來,如果 B.tsA.ts 依賴,那麼 B.ts 不能被 exclude 排除,除非 A.ts 也被排除了。

有一點要注意的是,編譯器不會引入疑似為輸出的檔案。比如,如果引入的檔案中包含 index.ts ,那麼 index.d.tsindex.js 就會被排除。通常來說,只有擴充名不一樣的檔案命名法是不推薦的。

tsconfig.json 也可以為空檔案,這種情況下會使用預設的編譯選項來編譯所有預設引入的檔案。

編譯選項

常用選項

選項欄位 型別 預設值 說明
allowJs boolean false 允許編譯 JS 檔案
checkJs boolean false 報告 JS 檔案中存在的型別錯誤需要配合 allowJs 使用
declaration boolean false 生成對應的 .d.ts 檔案
declarationDir string - 生成的 .d.ts 檔案存放路徑預設與 .ts 檔案相同
experimentalDecorators boolean false 啟用實驗功能-ES 裝飾器
jsx string Preserve .tsx 中支援 JSX :ReactPreserve詳細說明
jsxFactory string React.createElement jsx 設定為 React 時使用的建立函式
lib string[] - 編譯時引入的 ES 功能庫,包括:es5es6es7dom 等。如果未設定,則預設為: targetes5 時: ["dom", "es5", "scripthost"] targetes6 時: ["dom", "es6", "dom.iterable", "scripthost"]
module string target === "es3" or "es5" ?"commonjs" : "es6" 生成的模組形式:nonecommonjsamdsystemumdes6es2015esnext 只有 amdsystem 能和 outFile 一起使用 targetes5 或更低時可用 es6es2015
moduleResolution string module === "amd" or "system" or "es6" ? "classic" : "node" 模組解析方式,詳細說明
noImplicitAny boolean false 存在隱式 any 時拋錯
noImplicitReturns boolean false 不存在 return 時拋錯
noImplicitThis boolean false this 可能為 any 時拋錯
outDir string - 編譯生成的檔案存放路徑預設與 .ts 檔案相同
sourceMap boolean false 生成 .map 檔案
target string es3 生成 .js 檔案版本

附:官方完整的編譯選項列表

型別相關

型別相關的選項包括 typeRootstypes

有一個普遍的誤解,以為這兩個選項適用於所有的型別宣告檔案,包括使用者自定義的宣告檔案。其實不然。
這兩個選項只對通過 npm 安裝的宣告模組有效,使用者自定義的型別宣告檔案與它們沒有任何關係

宣告模組通常會包含一個 index.d.ts 檔案,或者其 package.json 設定了 types 欄位。

預設的,所有位於 node_modules/@types 路徑下的模組都會引入到編譯器。
具體來說是,./node_modules/@types../node_modules/@types../../node_modules/@types 等等。

typeRoots 用來指定預設的型別宣告檔案查詢路徑,預設為 node_modules/@types 。比如:

{
  "compilerOptions": {
    "typeRoots": ["./typings"]
  }
}

上面的配置會自動引入 ./typings 下的所有 TS 型別宣告模組,而不是 ./node_modules/@types 下的模組。

如果不希望自動引入 typeRoots 指定路徑下的所有宣告模組,那可以使用 types 指定自動引入哪些模組。比如:

{
  "compilerOptions": {
    "types" : ["node", "lodash", "express"]
  }
}

只會引入 nodelodashexpress 三個宣告模組,其它的宣告模組則不會被自動引入。
如果 types 被設定為 [] ,那麼將不會自動引入任何宣告模組。此時,如果想使用宣告模組,只能在程式碼中手動引入了。

請記住,自動引入只對包含全域性宣告的模組有效。比如 jQuery ,我們不用手動 import 或者 ///<reference/> 即可在任何檔案中使用 $ 的型別。再比如,對於 import `foo` ,編譯器會分別在 node_modulesnode_modules/@types 檔案下查詢 foo 模組和宣告模組。

基於此,如果想讓自定義宣告的型別不需要手動引入就可以在任何地方使用,可以將其宣告為全域性宣告 global ,然後讓 files 或者 include 包含即可。
比如:

declare global {
  const graphql: (query: TemplateStringsArray) => void;
  namespace Gatsby {
    interface ComponentProps {
      children: () => React.ReactNode,
      data: RootQueryType
    }
  }
}

這樣的話,就可以在任何地方直接使用 graphqlGatsby 對應的型別了。

配置複用

可以使用 extends 來實現配置複用,即一個配置檔案可以繼承另一個檔案的配置屬性。

比如,建立一個基礎的配置檔案 configs/base.json

{
  "compilerOptions": {
    "noImplicitAny": true,
    "strictNullChecks": true
  }
}

然後,tsconfig.json 就可以引用這個檔案的配置了:

{
  "extends": "./configs/base",
  "files": [
    "main.ts",
    "supplemental.ts"
  ]
}

這種繼承有兩種特點:

  • 繼承者中的同名配置會覆蓋被繼承者
  • 所有相對路徑都被解析為其所在檔案的路徑

參考資料