前端程式碼間容性問題解決方案
tsconfig.json 的權責轉變
前端程式碼間容性問題解決方案

tsconfig.json 的權責轉變
為了處理瀏覽器無法識別 TypeScript 獨有的型別系統(如 Interface、Type Aliases)或裝飾器語法等情況,開發流程必須將 TypeScript 轉換為瀏覽器可執行的 JavaScript。
原本這項任務是由 **tsc **(TypeScript Compiler) 負責,採用「檢查與編譯綑綁」的模式:在輸出程式碼前先掃描整個專案的型別邏輯,在確保所有變數與函式的呼叫都符合定義後才產出 JS 檔案。這種做法雖然確保了程式碼的嚴謹性,但在大型專案中,繁重的檢查過程會導致編譯速度大幅下降。
後來陸續出現像是 esbuild 與 SWC 等工具被用於取代 tsc 的轉譯功能,使「程式碼轉換」與「型別檢查」各別透過不同工具來完成:
- esbuild / SWC: 為了提升轉譯速度,這些工具會直接抹除 TS 上的所有型別標註,並將程式碼轉換為指定的 JavaScript 版本。
// 以 Vite 為例,透過定義瀏覽器版本,Vite 會透過 esbuild 來進行語法轉譯。
export default defineConfig({
build: {
target: ['es2022'],
// or target: ['chrome115', 'edge115', 'firefox121', 'safari17'],
},
...
});
tsconfig.json: 僅專門作為型別檢查的設定檔。開發者可透過 IDE(如 VS Code)讀直接讀取tsconfig.json提供靜態檢查提示。
tsconfig.json中 target 與 lib 的運作機制
- target:
target原本是用來作為tsc執行語法降轉的標準,但在引入其他編譯工具後,將不再實際影響編譯結果。- lib:宣告執行環境中可用的 API 定義(如 DOM、ESNext)。若未手動設定
lib,TypeScript 會根據target的版本自動同步 API 的使用上限。
{
"compilerOptions": {
"composite": true,
"declarationMap": true,
"emitDeclarationOnly": true,
"importHelpers": true,
"isolatedModules": true,
"lib": ["es2022"], // 若使用 ES2022 以上 API 會直接報錯
"module": "esnext",
"moduleResolution": "bundler",
"noEmitOnError": true,
"noFallthroughCasesInSwitch": true,
"noImplicitOverride": true,
"noImplicitReturns": true,
"noUnusedLocals": true,
"skipLibCheck": true,
"strict": true,
"target": "es2022", // 在降級過程中,哪些新語法節點必須被轉換為等價的舊語法模式
"customConditions": ["@org/source"]
}
}
如何指定 target 版本 ?
若是開發後端項目,由於伺服器的環境固定且可控,正確的 target 能使型別檢查器(Type Checker)在開發時就成功預警環境不支援的 API。因此,在設定 target 時有下方幾種參考方式:
[tsconfig/bases](https://github.com/tsconfig/bases)
這是一個 GitHub 上的開源項目,專為不同的運行環境(如 Node.js 20+、Node.js 24+)提供官方推薦的 target、lib 與編譯選項。對於後端開發而言,能確保開發環境與伺服器環境的語法支持完全一致。
[node/green](https://node.green/)
詳細展示了各版本 Node.js 對 ECMAScript 特性的原生支持率。若運作的是後端項目,由於不需理會碎片化的使用者執行環境,可以直接參考該網站數據來精準設定 target。這樣做的好處是能減少不必要的編譯轉換,讓程式碼以原生語法高效執行,同時在除錯(Debug)時看到的程式碼也更貼近原始碼,大幅提升後端開發的維護效率。
前端環境相容性問題解決
在前端開發中,受限於使用者瀏覽器版本的約束,若僅根據開發環境(Node.js)設定 target 將產生巨大風險。另外由於 AST 靜態分析的模糊性(如無法判定 data.at(0) 的對象類型)無法在編譯階段判定變數型別(例如無法得知 data.at(0) 的 data 究竟是 Array 還是自定義物件),導致傳統的自動補丁工具(如 Babel usage 模式)只能依賴盲目的文字匹配,頻繁引發誤報與體積失控。
為了解決這個硬傷並追求極致效能,現代工具如 esbuild 選擇專注於語法結構的改寫與降級,將補丁(Polyfill)工作交由後需插件做處理,以下是幾種目前常見的策略:
- 手動補丁
最原始的做法是靜態引入,也是 esBuild 官方推薦的做法,但缺點是維護成本極高,且無論使用者瀏覽器是否支援,都必須下載這些補丁,導致打包體積無謂膨脹。
It does not automatically add polyfills for new APIs that are not used by these environments. You will have to explicitly import polyfills for the APIs you need (e.g. by importing
[core-js](https://www.npmjs.com/package/core-js)). Automatic polyfill injection is outside of esbuild's scope.
如果想避免無效引入,可以在專案入口利用 ESM 特性進行特徵檢測,僅在偵測失敗時才動態按需加載以便在舊版環境會才下載補丁。
if (!Object.groupBy || !Array.prototype.at) {
await Promise.all([
!Object.groupBy && import('core-js/actual/object/group-by'),
!Array.prototype.at && import('core-js/actual/array/at')
]);
}
2. Babel useBuiltIns: 'usage'
原理是利用 Babel 遍歷 AST(抽象語法樹)進行全文掃描,只要發現特定語法特徵(e.g. .at()),便自動比對 browserslist 設定,在該檔案頂部按需插入對應的 core-js 模組。
同前述提到的問題,由於 Babel 在編譯階段只做靜態文字匹配,無法進行控制流分析或型別推導,導致了嚴重的誤報(False Positive)。此外,大規模專案對 node_modules 進行 AST 全文掃描,也會增加打包時間。
Instance/Prototype Methods (not solved, but it’s fine)
The way we handle this is to be conservative and be ok with some false positives because it would still be less than importing the whole polyfill. In the future we could be smarter about knowing whether a variable is a string/array or not via inferring types or with type annotations from TS/FLow.
官方給出的實踐建議是:保守接受誤報(False Positive),或在配置的 exclude 欄位中手動加入黑名單,甚至建議中大型複雜專案直接退回 useBuiltIns: 'entry' 模式。
@vitejs/plugin-legacy(Issue)
透過差異化分發(Differential Serving)產出兩套 Bundle:
- Modern Bundle(現代版):由 esbuild 高速產出,預設不包含補丁。
- Legacy Bundle(舊版):由 Babel 接管,進行全套 ES5 語法降級,並注入全套
core-js補丁與 SystemJS 載入器。
HTML 最終會透過 <script type="module"> 與 nomodule 進行分流。然而,支援 ESM 模組語法的瀏覽器(例如 Chrome 65),不代表支援最新的 API(如 Chrome 115 的 Object.groupBy)。 這導致使用舊版現代瀏覽器的用戶會下載到 0 補丁的 Modern Bundle」,導致 TypeError 。而官方為了防禦這種情況,因此強行引入一個名為 modernTargets 的基準線,並以此去掃描程式碼進行補丁,導致 Modern Bundle 仍被塞進眾多可能不必要的補丁,使開發人員需額外同步版本配置。
- **SWC **(Speedy Web Compiler) 模式
SWC 核心開發理念是為了解決 Babel 因 JavaScript 單執行緒限制所導致的效能瓶頸。在 Vite 環境下(如透過 unplugin-swc 等插件),SWC 可以接管原本由 esbuild 負責的轉譯工作,並在 env 配置中開啟 mode: 'usage' 進行自動補丁。
// vite.config.ts
import { defineConfig } from 'vite';
import swc from 'unplugin-swc';
export default defineConfig({
plugins: [
swc.vite({
env: {
targets: "Chrome >= 87", // 設定目標環境
mode: 'usage', // 自動按需注入
coreJs: "3.38"
}
})
]
});
SWC 的編譯速度雖然比 Babel **快上 20 倍**,但在中大型專案中存在嚴重的生態斷層。例如 @wyw-in-js/vite (Linaria) 等 CSS-in-JS 工具,其底層強制依賴 Babel 的 AST 節點結構進行靜態分析與樣式抽取。若專案直接強制遷移至 SWC,將面臨功能缺失、或必須引入額外橋接層而喪失速度優勢的困境。
- 運行時動態服務:Polyfill-as-a-Service
拋棄編譯期的分析,完全將補丁決策移至運行時(Runtime)
<script src="https://cdnjs.cloudflare.com/ajax/libs/polyfill/3.111.0/polyfill.min.js" crossorigin="anonymous"></script>
當使用者的瀏覽器發送請求時,邊緣伺服器(如 Cloudflare 或 Fastly)即時解析請求標頭中的 User-Agent,比對瀏覽器能力矩陣後,動態生成並回傳該環境缺失的補丁。
然而,使用第三方服務需額外考量供應鏈安全風險。由於直接在 HTML 中引用外部域名的 JS 檔案,一旦服務商的伺服器遭到入侵或域名控制權轉移,攻擊者就能植入惡意程式碼。像是 2024 年的 Polyfill.io 攻擊事件便是在該原始域名被收購後,伺服器開始向用戶端注入會重導向至詐騙或博弈網站的惡意腳本,導致全球數百萬個網站暴露在風險之中。
當前前端工具鏈在處理瀏覽器相容性與補丁(Polyfill)時,各自面臨不同的技術瓶頸:Babel 雖然功能完整,但因使用 JavaScript 編寫,在大專案中解析 AST(抽象語法樹)的效能極為低下;esbuild 雖憑藉 Go 語言實現了極速編譯,但它只負責語法降級,不具備 API 補丁功能, 需額外依賴基於 Babel 的相容性插件才能處理,引發雙重維護配置等問題;而用 Rust 重寫的 SWC 雖然解決了 Babel 的速度問題,但其補丁機制依然沿用傳統的靜態文字匹配,在缺乏型別推導的情況下,一旦遇到 node_modules 依賴或 Linaria 等工具降級後的特徵字串,同樣會產生誤補的情況。
Reference
메타데이터
- post_id
- b9277da3faa5
- slug
- 前端程式碼間容性問題解決方案-b9277da3faa5
- url
- https://medium.com/@rosiechen_2000/%E5%89%8D%E7%AB%AF%E7%A8%8B%E5%BC%8F%E7%A2%BC%E9%96%93%E5%AE%B9%E6%80%A7%E5%95%8F%E9%A1%8C%E8%A7%A3%E6%B1%BA%E6%96%B9%E6%A1%88-b9277da3faa5
- canonical_url
- https://medium.com/@rosiechen_2000/%E5%89%8D%E7%AB%AF%E7%A8%8B%E5%BC%8F%E7%A2%BC%E9%96%93%E5%AE%B9%E6%80%A7%E5%95%8F%E9%A1%8C%E8%A7%A3%E6%B1%BA%E6%96%B9%E6%A1%88-b9277da3faa5
- author_url
- https://medium.com/@rosiechen_2000
- status
- ok
- fetched_at
- 2026-06-22 12:55:45