會寫 TypeScript 但你真的會 TS 編譯配置嗎?

最近遇到了挺多涉及到前端“編譯”方面的工作,其中關(guān)于 TypeScript 的編譯會涉及到關(guān)于 tsconfig.json 文件的配置,由于配置項(xiàng)繁雜,遂逐一解析并驗(yàn)證,減少大家的一些疑惑,并提升工作效率!

image

隨著 TypeScript 的流行,越來越多的項(xiàng)目通過使用 TypeScript 來實(shí)現(xiàn)編寫代碼時候的類型提示和約束,從開發(fā)過程中減少 BUG 出現(xiàn)的概率,以此提升程序的健壯性和團(tuán)隊(duì)的研發(fā)效率。

為什么會單獨(dú)寫一篇文章來講述 tsconfig.json 文件的配置吶?原因是筆者在做 TS 項(xiàng)目的時候,由于對其中的配置項(xiàng)不熟悉,搞來搞去,搞好久,煩死了!所以決定好好梳理下。

image

越來越多的項(xiàng)目用上了 TypeScript,因此如何按需配置 tsconfig 也應(yīng)該是前端工程師需要掌握的技能之一。

本文內(nèi)容結(jié)構(gòu)如下,朋友們按需食用??:

image

一、前置知識 ??

在熟悉掌握 tsconfig.json 文件配置前,先給首次接觸 TS 的同學(xué)預(yù)備一下“前置知識”。

1.1 TypeScript 是什么?

TypeScript 官網(wǎng):https://www.typescriptlang.org

image

TypeScript 是一種基于 JavaScript強(qiáng)類型編程語言,它使得在前端項(xiàng)目開發(fā)過程中更加嚴(yán)謹(jǐn)且流暢,一定程度上保證了大型前端項(xiàng)目程序的健壯性。

  • TypeScript 是由微軟開發(fā)的一款開源的編程語言;
  • TypeScript 是 JavaScript 的超集,遵循最新的 ESM 規(guī)范,TypeScript 擴(kuò)展了 JavaScript 的語法;
  • TypeScript 更像后端 JAVA、C# 這樣的面向?qū)ο笳Z言,可以讓 JS 開發(fā)大型企業(yè)級項(xiàng)目。

但是 TypeScript 并不可以直接運(yùn)行,而是需要轉(zhuǎn)換成 JavaScript 代碼才可以在 Node.js 或?yàn)g覽器環(huán)境下執(zhí)行,因此我們需要通過“編譯器”將 TS 代碼轉(zhuǎn)換為 JS 代碼。

1.2 什么是 tsc ?

tsc 的全稱是 TypeScript Compiler,也就是將 TypeScript 轉(zhuǎn)碼為 JavaScript 代碼的編譯器。

tsc 的全局安裝方式:

npm install typescript -g

當(dāng)我們編譯一份 index.ts 文件時,會使用下面的命令:

tsc ./index.ts

這樣就可以得到一份編譯成為 JavaScript 代碼的 ./index.js 文件。

tsc 實(shí)際就是將 TS 轉(zhuǎn)為 JS 的編譯(器)腳手架工具,如果是一個 TS 的前端工程項(xiàng)目,那么就可以通過項(xiàng)目中的 tsconfig.json 文件來自定義配置 TS 編譯相關(guān)規(guī)則。

項(xiàng)目中的 tsconfig.json 文件,我們一般會通過如下快捷命令生成:

tsc --init

執(zhí)行完后,會在項(xiàng)目根目錄生成一個簡單的初始化 tsconfig.json 配置描述文件,如果沒有特別的要求,該初始化配置就足以支持你愉快地使用 TS 開發(fā)啦!

更多相關(guān) TS 編譯配置和使用說明可以通過 tsc -h 查看。

1.3 tsconfig.json 文件

tsconfig.json 文件是用于描述將 TypeScript 轉(zhuǎn)為 JavaScript 代碼的配置文件。

IDE(代碼編輯器)將會根據(jù) tsconfig.json 文件來對當(dāng)前項(xiàng)目中支持不同程度的類型約束,同時也是對 TSC 編譯 TypeScript 代碼過程做一些預(yù)定義、約束入口和編譯輸出目錄等配置。

因此對于一個支持 TypeScript 編程語言的工程來說,tsconfig.json 文件就是編碼的基礎(chǔ)。

二、tsconfig.json 配置詳解 ??

有了上面的前置知識作為基石,相信大家會對 tsconfig.json 文件的配置項(xiàng)也會更加容易理解。

tsconfig 協(xié)議

筆者將從常見的配置項(xiàng)單獨(dú)解釋,然后在最后會將一些不常用的配置統(tǒng)一解釋,朋友們可以將這篇文章收藏一下,可當(dāng)作一份 tsconfig 配置的中文查詢對照表 ??。

2.1 files

files 字段用于指明需要 tsc 編譯的一個或多個 ts 文件,例如:

{
  "files": ["index.ts", "global.d.ts"],
}

當(dāng)指定的文件或文件夾不存在時,會提示 ? 錯誤!

2.2 include

include 字段用于指明需要被 tsc 編譯的文件或文件夾列表,例如:

{
  "include": [
    "src",
    "global.d.ts"
  ],
}

2.3 exclude

exclude 字段用于排除不需要 tsc 編譯的文件或文件夾列表,例如:

{
  "exclude": ["test.ts", "src/test.ts"],
}

注意: exclude 字段中的聲明只對 include 字段有排除效果,對 files 字段無影響,即與 include 字段中的值互斥。

如果 tsconfig.json 文件中 filesinclude 字段都不存在,則默認(rèn)包含 tsconfig.json 文件所在目錄及子目錄的所有文件,且排除在 exclude 字段中聲明的文件或文件夾。

2.4 compileOnSave

compileOnSave 是聲明是否需要在保存時候自動觸發(fā) tsc 編譯的字段,一般來說,我們的代碼編譯過程會通過 Rollup、Webpack 等打包構(gòu)建工具,并且使用熱更新,因此無需配置該項(xiàng),保持缺省即可。

{
  "compileOnSave": false,
}

2.5 extends

extends 字段用于指明繼承已有的 tsconfig 配置規(guī)則文件。

該字段可以說是非常有用了,因?yàn)槲覀兊?tsconfig 配置其實(shí)各個項(xiàng)目之間大同小異,因此完全可以結(jié)合自己團(tuán)隊(duì)的情況,抽離一個基礎(chǔ)且公共的 tsconfig 配置,并將其發(fā)包,然后作為 extends 字段的值來繼承配置。

tsconfig 推薦默認(rèn)配置可以參考官方的包:@tsconfig/recommended

@tsconfig/recommended 的配置如下:

{
  "compilerOptions": {
    "target": "ES2015",
    "module": "commonjs",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "$schema": "https://json.schemastore.org/tsconfig",
  "display": "Recommended"
}

例如繼承一個發(fā)包后的 tsconfig 基礎(chǔ)配置,并通過顯示聲明編譯的目標(biāo)代碼版本為 ES2016 來覆蓋覆蓋 @tsconfig/recommended 中對應(yīng)配置項(xiàng)。

{
  "extends": "@tsconfig/recommended/tsconfig.json",
  "compilerOptions": {
    "target": "ES2016"
  }
}

作為一些實(shí)踐經(jīng)驗(yàn),社區(qū)也提供了一些常見環(huán)境(例如:Nuxt、Vite、Node 等)最佳實(shí)踐后的基礎(chǔ)配置,推薦參閱:https://github.com/tsconfig/bases/

2.6 compilerOptions

compilerOptions 是一個描述 TypeScript 編譯器功能的“大”字段,其值類型是“對象”,因此包含了很多用于描述編譯器功能的子字段,其子字段的功能如下:

(1). target

target 字段指明經(jīng)過 TSC 編譯后的 ECMAScript 代碼語法版本,根據(jù) ECMAScript 語法標(biāo)準(zhǔn),默認(rèn)值為 ES3。

TypeScript 是 JavaScript 的超集,是對 JavaScript 語法和類型上的擴(kuò)展,因此我們可以使用 ES5、ES6,甚至是最新的 ESNext 語法來編寫 TS。例如當(dāng)我們使用 ES2021 語法來編碼 TS 文件,同時配置如下:

{
  "compilerOptions": {
    "target": "ES5",
  }
}

則會將對應(yīng)使用了最新 ECMAScript 語法的 TS 文件編譯為符合 ES5 語法規(guī)范的 *.js 文件。

延伸一下知識點(diǎn),思考一下 tsc 是如何將高版本(ECMAScript 規(guī)范)代碼向低版本代碼轉(zhuǎn)換的?這個轉(zhuǎn)換的結(jié)果靠譜嗎?與 Babel 有何差異?

一圖看 ECMAScript 各版本功能差異

另外對于個版本差異有想簡單了解的??,可以閱讀《1.5萬字概括ES6全部特性

通過一個實(shí)驗(yàn),在 src/index.ts 文件中使用了 Map、Async/Await、Promise、擴(kuò)展運(yùn)算符,并在 tsconfig.jon -> target 設(shè)置為 ES5

驗(yàn)證 target 降級處理

然后發(fā)現(xiàn)在右側(cè)的 dist/index.js 文件中,依然存在 new Map() 、Promise 語法,因此可以得出結(jié)論:tsc 的代碼降級編譯并不能完全處理兼容性

通過官方文檔了解到:

image

這里提到了 lib 字段,意思是 target 不同的值會有對應(yīng)默認(rèn)的 lib 字段值,當(dāng)然也支持開發(fā)者顯示指明 lib 字段的值,那么接下來看看 lib 是干嘛的吧!

(2). lib

lib 字段是用于為了在我們的代碼中顯示的指明需要支持的 ECMAScript 語法或環(huán)境對應(yīng)的類型聲明文件。

例如我們的代碼會使用到瀏覽器中的一些對象 window、document,這些全局對象 API 對于 TypeScript Complier 來說是不能識別的:

lib 未顯示引入 DOM 會提示類型錯誤

因而需要在 lib 字段中如下配置:

{
  "compilerOptions": {
    "target": "ES5",
    "lib": ["ES5", "ES6", "DOM"],
  }
}

來顯式引入在 DOM 即瀏覽器環(huán)境下的一些默認(rèn)類型定義,即可在代碼中使用,window、document 等瀏覽器環(huán)境中的對象,TS 在運(yùn)行時以及編譯時就不會報類型錯誤。

引入類型定義后無錯誤提示

綜合 targetlib 字段的實(shí)際功能表現(xiàn),我們可以得出結(jié)論

TSC 的編譯結(jié)果只有部分特性做了 pollyfill 處理,ES6 的一些特性仍然被保留,想要支持完全的降級到 ES5 還是需要額外引入 pollyfill(也就是我們在項(xiàng)目的入口文件處 import 'core-js'),但建議是將 target 字段值設(shè)置為 ES6,提升 TSC 的速度。

因此,筆者對于使用 TSC 編譯的觀點(diǎn)是:

不應(yīng)該將 TSC 作為編譯項(xiàng)目的工具,應(yīng)該將 TSC 作為類型檢查工具,代碼編譯的工作盡量交給 Rollup、WebpackBabel 等打包工具!

另外推薦閱讀《為什么說用 babel 編譯 typescript 是更好的選擇

(3). module

module 字段指明 tsc 編譯后的代碼應(yīng)該符合何種“模塊化方案”,可以指定的枚舉值有:none, commonjs, amd, system, umd, es2015, es2020, 或 ESNext,默認(rèn)值為 none

在如今的前端開發(fā)趨勢來講,主要是使用 ESM、CommonJS、UMD、IIFE 四種模塊化方案,未來會趨向于 ESM,當(dāng)然我們會根據(jù)項(xiàng)目的應(yīng)用場景來決定使用何種模塊化方案,例如:NodeJS 使用 CommonJS,瀏覽器里可以使用 ESM,不過現(xiàn)在的打包工具,會自動處理 CommonJS 和 ESM 的差異,并包裝成符合指定模塊化規(guī)范的代碼,

在 tsconfig.json 可以設(shè)置 allowSyntheticDefaultImports 字段為 true,來允許合成默認(rèn)導(dǎo)入。

(4). esModuleInterop

簡單來說,就是支持合成默認(rèn)導(dǎo)入。

在前端項(xiàng)目開發(fā)時,使用 ESM 編寫代碼引入了 CJS 的模塊,由于 CJS 模塊沒有默認(rèn)導(dǎo)出內(nèi)容,因此需要通過我們的工具去自動化合成 CJS 的默認(rèn)導(dǎo)出,以支持在 ESM 下流暢開發(fā)。

參閱文章《esModuleInterop 到底做了什么?》,講得非常詳細(xì)也非常好。

當(dāng) esModuleInterop 字段設(shè)置為 true 時候,上述提到的 allowSyntheticDefaultImports 字段也會自動設(shè)置為 true

(5). moduleResolution

moduleResolution 聲明如何處理模塊,枚舉值:classic、node,會根據(jù) module 字段決定默認(rèn)值。

推薦手動設(shè)置為 node,更符合現(xiàn)在大家的編碼認(rèn)識一些,而且大部分的構(gòu)建打包工具都是基于 Node。

舉個??,遇到 import {a} from 'a-lib'; 這樣的模塊引入代碼應(yīng)該如何去(解析)查找到對應(yīng)的模塊文件。

(6). baseUrl & paths

baseUrl:設(shè)置基本目錄以解析非絕對模塊名稱(定義一個根目錄,以此進(jìn)行絕對文件路徑解析)

paths:用于設(shè)置模塊名或路徑映射列表,這樣就可以簡寫項(xiàng)目中自定義模塊的文件路徑。

舉一個 ??:

{
  "compilerOptions": {
    // 注意:baseUrl 必選,與 paths 成對出現(xiàn),以 tsconfig.json 文件所在目錄開始
    "baseUrl": ".", 
    "paths": {
      // 映射列表
      "@/*": [
        "src/*"
      ],
      "moduleA": [
        "src/libs/moduleA"
      ]
    }
  }
}

// 代碼里這么寫
import Toast from '@/components/Toast.ts' // 模塊實(shí)際位置: src/components/Toast.ts
import TestModule from 'moduleA/index.js' // 模塊實(shí)際位置: src/libs/moduleA/index.js

?? 注意: 如果需要自動生成(導(dǎo)出)類型定義文件,TSC 不會處理路徑別名,需要引入 typescript-transform-paths 插件,以及 TTypescript 來轉(zhuǎn)換路徑別名為相對路徑。

由于當(dāng)前的 TypeScript 不支持 tsconfig.json 中的自定義轉(zhuǎn)換器,且無法使用 tsc 命令使用自定義轉(zhuǎn)換器編譯文件,所以引入了 TTypescript 作為包裝器

// tsconfig.json
{
  "compilerOptions": {
    "baseUrl": "./",
    // 配置路徑別名映射
    "paths": {
      "@/*": ["src/*"]
    },
    "plugins": [
      // 轉(zhuǎn)換輸出 js 文件中的路徑
      { "transform": "typescript-transform-paths" },

      // 轉(zhuǎn)換輸出 .d.ts 文件中的路徑
      { "transform": "typescript-transform-paths", "afterDeclarations": true }
    ]
  }
}

plugins 是用于擴(kuò)展 TSC 編譯器功能的字段。

例如在 Rollup 打包環(huán)境下,可以如下配置:

import typescript from '@rollup/plugin-typescript';
import ttypescript from 'ttypescript';

export default [
  {
    input: './src/index.ts',
    output: {
      dir: 'dist',
      format: 'cjs',
      entryFileNames: 'index.js',
    },
    plugins: [
      typescript({
        typescript: ttypescript,
      }),
    ],
  },
];

如果是有自動導(dǎo)出類型定義文件的需求,才需要搞這一套插件~

(7). rootDir & outDir

rootDir:指定 TypeScript 識別讀取的根目錄,用于所有非聲明輸入文件的最長公共路徑

例如:'"rootDir": "./src",則 src 目錄下的 TS 文件不能引用 src 目錄以外的 ts 文件,一般我們會設(shè)置為 ./src./(即 tsconfig.json 所在目錄)

outDir:輸出目錄,即 tsc 編譯后的文件輸出的文件夾路徑(基于 tsconfig.json 文件的相對路徑)

例如:"outDir": "./dist",及將 TSC 編譯輸出的 JS 文件,統(tǒng)一輸出的 ./dist 目錄下。

(8). jsx

如果是有 jsx 語法需要支持的項(xiàng)目,可以設(shè)置值 preserve、react

{
  "compilerOptions": {
    "jsx": "preserve", // 一般 preserve 即可
  },
}

(9). importHelpers

importHelpers 決定是否啟用從 tslib 庫引入語法降級輔助函數(shù),以避免重復(fù)冗余的輔助函數(shù)聲明。

個人建議是設(shè)置為 true 來啟用。

(10).experimentalDecorators

experimentalDecorators 用于聲明是否啟實(shí)驗(yàn)性用裝飾器模式。

TypeScript 和 ES6 中引入了 Class 的概念,同時在 Decorators 提出了裝飾器模式,通過引入裝飾器模式,能極大簡化書寫代碼。

當(dāng)前對于 Decorator 的支持性不太好,如果是一些涉及到使用了裝飾器的需要,就需要開啟這個屬性。

(11). noEmit

noEmit 設(shè)置是否輸出 js 文件,一般是設(shè)置為 false,將打包等工作交給 Webpack 等工具。

三、tsconfig.json 全解析 ??

上面針對 tsconfig.json 中一些常見配置做了詳細(xì)解釋,將一些不常用的配置字段組合在一起,做一個 Checklist 如下:

{
  "compilerOptions": {
    /* 基本選項(xiàng) */
    "target": "es6", // 指定 ECMAScript 目標(biāo)版本: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'
    "module": "commonjs", // 指定使用模塊: 'commonjs', 'amd', 'system', 'umd' or 'es2015'
    "lib": [], // 指定要包含在編譯中的庫文件
    "allowJs": true, // 允許編譯 javascript 文件
    "checkJs": true, // 報告 javascript 文件中的錯誤
    "jsx": "preserve", // 指定 jsx 代碼的生成: 'preserve', 'react-native', or 'react'
    "declaration": true, // 生成相應(yīng)的 '.d.ts' 文件
    "declarationDir": "./dist/types", // 生成的 '.d.ts' 文件保存文件夾
    "sourceMap": true, // 生成相應(yīng)的 '.map' 文件
    "outFile": "./", // 將輸出文件合并為一個文件
    "outDir": "./dist", // 指定輸出目錄
    "rootDir": "./", // 用來控制輸出目錄結(jié)構(gòu) --outDir.
    "removeComments": true, // 刪除編譯后的所有的注釋
    "noEmit": true, // 不生成輸出文件
    "importHelpers": true, // 從 tslib 導(dǎo)入輔助工具函數(shù)
    "isolatedModules": true, // 將每個文件做為單獨(dú)的模塊 (與 'ts.transpileModule' 類似).

    /* 嚴(yán)格的類型檢查選項(xiàng) */
    "strict": true, // 啟用所有嚴(yán)格類型檢查選項(xiàng)
    "noImplicitAny": true, // 在表達(dá)式和聲明上有隱含的 any類型時報錯
    "strictNullChecks": true, // 啟用嚴(yán)格的 null 檢查
    "noImplicitThis": true, // 當(dāng) this 表達(dá)式值為 any 類型的時候,生成一個錯誤
    "alwaysStrict": true, // 以嚴(yán)格模式檢查每個模塊,并在每個文件里加入 'use strict'

    /* 額外的檢查 */
    "noUnusedLocals": true, // 有未使用的變量時,拋出錯誤
    "noUnusedParameters": true, // 有未使用的參數(shù)時,拋出錯誤
    "noImplicitReturns": true, // 并不是所有函數(shù)里的代碼都有返回值時,拋出錯誤
    "noFallthroughCasesInSwitch": true, // 報告switch語句的fallthrough錯誤。(即,不允許switch的case語句貫穿)

    /* 模塊解析選項(xiàng) */
    "moduleResolution": "node", // 選擇模塊解析策略: 'node' (Node.js) or 'classic' (TypeScript pre-1.6)
    "baseUrl": "./", // 用于解析非相對模塊名稱的基礎(chǔ)目錄
    "paths": {}, // 模塊名到基于 baseUrl 的路徑映射的列表
    "rootDirs": [], // 根文件夾列表,其組合內(nèi)容表示項(xiàng)目運(yùn)行時的結(jié)構(gòu)內(nèi)容
    "typeRoots": [], // 包含類型聲明的文件列表
    "types": [], // 需要包含的類型聲明文件名列表
    "allowSyntheticDefaultImports": true, // 允許從沒有設(shè)置默認(rèn)導(dǎo)出的模塊中默認(rèn)導(dǎo)入。
    "esModuleInterop": true, // 支持合成模塊的默認(rèn)導(dǎo)入
  
    /* Source Map Options */
    "sourceRoot": "./", // 指定調(diào)試器應(yīng)該找到 TypeScript 文件而不是源文件的位置
    "mapRoot": "./", // 指定調(diào)試器應(yīng)該找到映射文件而不是生成文件的位置
    "inlineSourceMap": true, // 生成單個 soucemaps 文件,而不是將 sourcemaps 生成不同的文件
    "inlineSources": true, // 將代碼與 sourcemaps 生成到一個文件中,要求同時設(shè)置了 --inlineSourceMap 或 --sourceMap 屬性

    /* 其他選項(xiàng) */
    "experimentalDecorators": true, // 啟用裝飾器
    "emitDecoratorMetadata": true // 為裝飾器提供元數(shù)據(jù)的支持
  },
  /* 指定編譯文件或排除指定編譯文件 */
  "include": ["src/**/*"],
  "exclude": ["node_modules", "**/*.spec.ts"],
  "files": ["index.ts", "test.ts"],
  // 從另一個配置文件里繼承配置
  "extends": "@tsconfig/recommended",
  // 讓 IDE 在保存文件的時候根據(jù) tsconfig.json 重新生成文件
  "compileOnSave": true // 支持這個特性需要Visual Studio 2015, TypeScript 1.8.4 以上并且安裝 atom-typescript 插件
}

四、打包工具中的 TypeScript ??

前文講到了為什么不推薦直接使用 TSC 作為項(xiàng)目的打包編譯工具,那么接下來就簡單看看在常見的幾款打包工具中針對 TypeScript 的編譯方案是如何設(shè)計(jì)的?

4.1 Rollup + TypeScript

在 Rollup 打包中,我們一般只需要添加 @rollup/plugin-typescript 插件即可,該插件會默認(rèn)讀取項(xiàng)目根目錄下的 tsconfig.json 配置文件。

Rollup 的配置就像這樣:

// file: rollup.config.js
import typescript from '@rollup/plugin-typescript';

export default {
  input: 'src/index.ts',
  output: {
    dir: 'output',
    format: 'cjs'
  },
  plugins: [typescript()]
};

結(jié)合其源碼:

默認(rèn)使用 TSC 作為 TS 的編譯器

因?yàn)?typescript 聲明了是 peerDependencies,因此會采用項(xiàng)目中安裝的 typescript 版本,即是使用我們項(xiàng)目中的 TS 編譯器。

通過閱讀 @rollup/plugin-typescript 源碼,可以看到該插件會默認(rèn)使我們自己項(xiàng)目中的 tsconfig.json 文件作為 TSC 編譯的配置,但會做一些配置預(yù)設(shè)覆蓋:

會調(diào)用 ts.parseJsonConfigFileContent() 方法,將 FORCED_COMPILER_OPTIONS 值 merge 到用戶的自定義配置中。

FORCED_COMPILER_OPTIONS

通過英文解釋看到,因?yàn)樾枰?TSC 編譯獲得 JS 產(chǎn)物,所以會將 noEmit 設(shè)置為 false,也就是 TSC 編譯會輸出文件,但為什么我們在輸出目錄卻沒有看到對應(yīng)的 TSC 產(chǎn)物吶?

TSC 編譯結(jié)果存儲到內(nèi)存中

但是如果開啟了 declaration,則會將 TSC 解析得到的 *.d.ts 文件輸出到指定目錄。

4.2 Webpack + TypeScript

Webpack 中的 TypeScript 官方文檔中,指明了需要安裝:typescriptts-loader 兩個模塊。

配置 Webpack 并支持 TypeScript 的配置如下:

// file: webpack.config.js
const path = require('path');

module.exports = {
  entry: './src/index.ts',
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        use: 'ts-loader',
        exclude: /node_modules/,
      },
    ],
  },
  resolve: {
    extensions: ['.tsx', '.ts', '.js'],
  },
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist'),
  },
};

可以看出 Webpack 主要是依賴 ts-loader 實(shí)現(xiàn)對 TypeScript 語法的編譯支持,再看看對 ts-loader 的介紹:

ts-loader

換句話說,ts-loader 實(shí)際調(diào)用了 TSC 來編譯 TS 文件,TSC 的配置依賴于你項(xiàng)目中的 tsconfig.json 文件。

如果使用了 Babel,則可以使用 @babel/preset-typescript 來處理,但 Babel 不會做 TS 類型校驗(yàn),在打包工具 Rollup 和 Webpack 中都可以引入 Babel,那么接下來看看 Babel 是如何處理 TypeScript 的吧!

4.3 Babel + TypeScript

Babel 處理 TS 需要安裝 @babel/preset-typescript 模塊,然后在 babel 項(xiàng)目配置文件中聲明:

// 配置說明:https://babeljs.io/docs/en/babel-preset-typescript
{
  "presets": ["@babel/preset-typescript"]
}

但 Babel 中只會對 TS 代碼轉(zhuǎn)為 JS 代碼(通過 parse TS 文件為 AST,并直接移除類型信息,然后打印目標(biāo)代碼),不會去做 TS 類型檢查,所以 Babel 編譯 TS 文件相較于 TSC 的速度更快!

同時,因?yàn)?Babel 會根據(jù)不同的兼容環(huán)境,按需引入 pollyfill,比 TSC 直接引入 core-js 更優(yōu)雅,因此使用了 Babel 打包的體積也會更小。

TS 類型檢查工作可以交給代碼編輯器承擔(dān),當(dāng)然同時可以新增 TS 檢查的命令:

// package.json
{
  "script": {
    "tsCheck": "tsc --noEmit",
  }
}

可以把類型檢查放到特定的 npm scripts 生命周期之前,另外其實(shí)也可以將類型檢查放到 git commit 階段,用于做必要的 TS 類型檢查,保證項(xiàng)目的正確性。

4.4 ESbuild + TypeScript

通過 Vite 體會到了 ESbuild 帶來的開發(fā)熱更新“極速”體驗(yàn),針對 TS 項(xiàng)目,ESbuild 和 Babel 是相同的編譯策略,即僅編譯,不校驗(yàn)類型。

ESbuild 處理 TypeScript 同樣可以帶來飛一般的感覺!

Vite 使用 esbuild 將 TypeScript 轉(zhuǎn)譯到 JavaScript,約是 tsc 速度的 20~30 倍,同時 HMR 更新反映到瀏覽器的時間小于 50ms?!?Vite Docs

但在 ESbuild 中需要啟用 tsconfig 中的 isolatedModules 功能,然后在類型引入的時候需要替換,規(guī)則參考如下:

// old
import { UserType } from './types';

// new
import type { UserType } from './types';

因?yàn)?ESbuild 是單獨(dú)編譯每個文件,無法判斷引入的是 Type(類型) 還是 值,所以需要開發(fā)者顯示地聲明是“Type”。

同時還需要啟用 esModuleInterop 功能,用于支持 ESM 模塊合成默認(rèn)導(dǎo)入,以兼容 CJS 和 ESM 規(guī)范。

另外 ESbuild 不支持:emitDecoratorMetadat、const enum 類型和 *.d.ts 文件

此外,關(guān)注到兼容性處理這方面,Bable 和 ESbuild 是類似的,因此會存在兼容性問題:

兼容性

對于裝飾器處理不支持,因?yàn)?TS 是 JS 的超集,ESnext 的規(guī)范提案某些還不是穩(wěn)定的,因此如果有這方面訴求的項(xiàng)目,可以借助 TSC 做預(yù)編譯,例如使用 Rollup 的 typescript 插件 或 Webpack 的 ts-loader 方式。

五、總結(jié) ??

針對 TypeScript 項(xiàng)目的類型檢查和編譯流程算是完整過了一遍,相信已足以支撐大家在工作中自定義化配置 TS 前端項(xiàng)目!

另外,tsconfig.json 推薦配置策略如下:

  1. 借助 extends 字段,并結(jié)合項(xiàng)目應(yīng)用場景,繼承官方推薦配置
  2. 針對項(xiàng)目特點(diǎn),按需修改對應(yīng)功能配置
  3. 建議啟用 importHelpers、esModuleInterop,取消 noEmit 輸出
  4. TS 項(xiàng)目的打包構(gòu)建,推薦使用 Webpack、Rollup、Bable 等專業(yè)工具來保證正確性和構(gòu)建優(yōu)化
image

朋友們可以關(guān)注微信公眾號:DYBOY,來一起玩耍呀~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容