本文 AI 產出,尚未審核

TypeScript 與 JavaScript 整合(JS Interop)

主題:allowJs / checkJs


簡介

在現代前端開發中,TypeScript 已經成為主流語言,它提供靜態型別、編譯期錯誤檢查以及更佳的 IDE 體驗,讓大型專案更易維護。然而,絕大多數既有的程式碼庫仍是 純 JavaScript,直接把專案全部改寫成 TypeScript 既不實際也不划算。此時,allowJscheckJs 兩個 tsconfig.json 設定,就成為 JS 與 TS 混合開發 的關鍵橋樑。

  • allowJs:允許編譯器把 .js 檔案納入編譯流程,讓 TypeScript 能夠 引用輸出 JavaScript 檔案。
  • checkJs:在允許 JavaScript 的同時,對這些檔案執行 型別檢查,彷彿它們本身就是 .ts 檔案。

掌握這兩個選項,開發者可以 逐步遷移混合使用,甚至在純 JavaScript 專案中逐漸加入型別安全,而不必一次性重構全部程式碼。


核心概念

1. allowJs:讓 TypeScript 接納 JavaScript

tsconfig.json 中加入:

{
  "compilerOptions": {
    "allowJs": true,
    "outDir": "./dist"
  },
  "include": ["src/**/*"]
}
  • allowJs: true 會把 src 目錄下的 .js 檔案視為編譯單位,最終會被 轉譯(依據 target 設定)並輸出到 outDir
  • 若同時存在同名的 .ts.js,編譯器會優先使用 .ts,避免衝突。

小技巧:在大型專案中,可以先把所有舊有的 JavaScript 放在 src/js,再逐步搬移到 src/ts,利用 include/exclude 控制範圍。

2. checkJs:在 JavaScript 中啟動型別檢查

{
  "compilerOptions": {
    "allowJs": true,
    "checkJs": true,
    "noEmit": true   // 只檢查,不產出檔案
  },
  "include": ["src/**/*.js"]
}
  • checkJs: true 會針對 .js 檔案執行 完整的型別檢查,包括未宣告變數、錯誤的函式呼叫、參數類型不符等。
  • 常與 // @ts-check 註解搭配使用,讓個別檔案自行決定是否啟用檢查。
// @ts-check
function add(a, b) {
  return a + b;
}
add(1, "2"); // ❌ TypeScript 會報錯:Argument of type 'string' is not assignable to parameter of type 'number'.

3. // @ts-nocheck// @ts-ignore

  • // @ts-nocheck:整個檔案關閉型別檢查,適合暫時跳過難以改寫的舊程式。
  • // @ts-ignore:僅對下一行忽略型別錯誤,適用於少量例外情況。
// @ts-nocheck
// 這裡的程式碼不會被 TypeScript 檢查

// @ts-ignore
someLegacyFunction(); // 忽略此行的型別錯誤

4. declare 與 JSDoc:為 JavaScript 補足型別資訊

即使開啟 checkJs,若沒有額外資訊,TypeScript 只能推斷 any。使用 JSDocdeclare 可以手動提供型別。

/**
 * @typedef {Object} User
 * @property {string} name 使用者名稱
 * @property {number} age  年齡
 */

/**
 * 取得使用者資料
 * @param {number} id 使用者 ID
 * @returns {Promise<User>}
 */
async function fetchUser(id) {
  const resp = await fetch(`/api/user/${id}`);
  return resp.json(); // TypeScript 會正確推斷回傳型別為 Promise<User>
}

程式碼範例

範例 1:從純 JavaScript 專案逐步加入 TypeScript

# 1. 初始化 TypeScript
npm i -D typescript
npx tsc --init

tsconfig.json(只開 allowJs,先不檢查):

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "commonjs",
    "allowJs": true,
    "outDir": "dist"
  },
  "include": ["src/**/*"]
}

現在執行 npx tsc,所有 .js 會被編譯成 .js(依設定的 target),同時保留原本的 JavaScript 語法。

範例 2:在 JavaScript 中開啟型別檢查,並使用 JSDoc

{
  "compilerOptions": {
    "allowJs": true,
    "checkJs": true,
    "noEmit": true,
    "strict": true
  },
  "include": ["src/**/*.js"]
}

src/math.js

// @ts-check

/**
 * 計算兩數的乘積
 * @param {number} a
 * @param {number} b
 * @returns {number}
 */
function multiply(a, b) {
  return a * b;
}

multiply(2, 3);   // ✅ 正確
multiply(2, "3"); // ❌ TypeScript 會提示錯誤

範例 3:混合使用 .ts.js,利用 declare 共享型別

src/types.d.ts(全域宣告):

declare module "legacy-lib" {
  export function legacyAdd(a: number, b: number): number;
}

src/legacy.js(舊函式庫):

// @ts-check
/** @type {any} */
const legacy = require("legacy-lib");

/**
 * 包裝 legacyAdd,使其支援型別檢查
 * @param {number} x
 * @param {number} y
 * @returns {number}
 */
function safeAdd(x, y) {
  return legacy.legacyAdd(x, y);
}

src/app.ts(新程式):

import { safeAdd } from "./legacy";

const sum = safeAdd(5, 10); // ✅ 型別正確

範例 4:使用 // @ts-nocheck 暫時關閉檢查

// @ts-nocheck
// 這段程式碼暫時不受 TypeScript 檢查,避免大量錯誤噴出

function oldApi(data) {
  // 內部大量使用 any、未宣告變數…
}

範例 5:利用 noEmit 僅做型別檢查,不產出檔案

{
  "compilerOptions": {
    "allowJs": true,
    "checkJs": true,
    "noEmit": true,
    "strict": true
  },
  "include": ["src/**/*.js"]
}

執行 npx tsc 後,不會產生 dist,但會在終端機顯示所有型別錯誤,適合 CI/CD 中作為 靜態分析 步驟。


常見陷阱與最佳實踐

陷阱 可能的結果 解決方案 / 最佳實踐
忘記在 tsconfig.json 加入 allowJs 編譯時找不到 .js 檔案,出現 File not found 錯誤。 確認 allowJstrue,或在 include/exclude 中正確指定路徑。
checkJs 導致大量 any 警告 開發者容易被噪音淹沒,失去信心。 先使用 // @ts-nocheck 包住舊檔案,逐步加入 JSDoc,或把 noImplicitAny 暫時關閉,待逐步改善。
混用 .js.ts 時模組解析衝突 同名檔案產生兩個不同的模組,導致 duplicate identifier 保持檔名唯一,或使用 paths/baseUrl 重新映射。
忘記在 JSDoc 中加上 @type TypeScript 只能推斷為 any,失去檢查價值。 為所有公開函式、物件加上完整的 JSDoc 型別描述。
checkJs 開啟時未設 strict 某些錯誤仍被寬容(例如隱式 any)。 建議同時開啟 strict,或至少 noImplicitAnystrictNullChecks

最佳實踐總結

  1. 先允許 (allowJs) 再逐步檢查 (checkJs)——循序漸進避免一次性衝擊。
  2. 使用 JSDoc 為舊程式提供型別訊息,配合 @ts-check 讓編譯器能讀取。
  3. 在 CI 中加入 noEmit && checkJs,確保每次提交都有型別安全的保證。
  4. 分層管理:把已遷移的檔案放在 src/ts,未遷移的仍在 src/js,讓 include/exclude 清晰可控。
  5. 適度使用 // @ts-ignore 只在必要且暫時的情況下,避免濫用成為「逃避型別」的捷徑。

實際應用場景

場景 為何使用 allowJs / checkJs
大型遺留系統逐步升級 允許舊有 JavaScript 繼續編譯,同時在新模組中使用 TypeScript,減少一次性改寫成本。
第三方 JavaScript 套件未提供型別宣告 透過 checkJs 搭配 JSDoc,為套件自行加上型別,提升 IDE 補完與錯誤偵測。
前端微服務(Micro‑frontend)共用代碼 各子應用可能使用不同語言(TS/JS),allowJs 能讓主程式統一編譯、打包。
CI/CD 靜態檢查 在建置流程中僅執行 npx tsc --noEmit,即使是純 JavaScript,也能捕捉潛在錯誤。
教育或培訓環境 讓新手先寫 JavaScript,透過 @ts-check 逐步感受型別系統的好處,降低學習門檻。

總結

allowJscheckJsTypeScript 與 JavaScript 混合開發 提供了兩條重要的橋樑:

  1. allowJs 讓編譯器接受任何 .js 檔案,確保舊有程式碼仍能被編譯、打包。
  2. checkJs 則在此基礎上加入 靜態型別檢查,讓開發者在不改寫語法的前提下,逐步提升代碼品質。

透過 JSDocdeclare、以及 // @ts-check / // @ts-nocheck 等輔助工具,我們可以在 逐步遷移混合模組CI 靜態分析 等情境中,安全且有效地引入 TypeScript 的優勢。只要遵循 先允許、後檢查、分層管理 的策略,即可在不犧牲開發速度的同時,逐步建立起嚴謹的型別系統,為專案的長期維護奠定堅實基礎。

關鍵一句話「讓 JavaScript 先跑,再讓 TypeScript 幫你把錯誤抓起來」,這正是 allowJscheckJs 在實務開發中的核心價值。祝你在混合開發的路上,越走越順!