Menu

tsconfig.json là gì? Các tùy chọn, mặc định và ví dụ

tsconfig.json đánh dấu một thư mục là dự án TypeScript và đặt các tùy chọn cho trình biên dịch. Các tùy chọn quan trọng (target, module, moduleResolution, strict, rootDir, outDir, include, lib, types, noEmit, skipLibCheck), một cấu hình khởi đầu nên dùng, extends và những gì TypeScript 7 đã thay đổi.

Trang này có trình soạn thảo chạy trực tiếp. Sửa, chạy và xem đầu ra ngay lập tức.

tsconfig.json là tệp cấu hình của một dự án TypeScript. Khi bạn chạy tsc không kèm đối số, trình biên dịch tìm tệp này trong thư mục hiện tại (rồi đến các thư mục cha), đọc các tùy chọn trong compilerOptions và kiểm tra những tệp mà include liệt kê. Một tệp nhỏ nhưng đầy đủ:

{
    "compilerOptions": {
        "target": "es2022",
        "module": "nodenext",
        "strict": true,
        "rootDir": "./src",
        "outDir": "./dist"
    },
    "include": ["src"]
}

Cấu hình này biên dịch mọi tệp TypeScript trong src thành JavaScript trong dist, ở dạng code ES2022, theo quy tắc module của Node.js, với mọi kiểm tra nghiêm ngặt được bật. npx tsc --init tạo một tệp khởi đầu dài hơn, có chú thích cho từng tùy chọn.

Tệp này là JSON có chú thích: chú thích //, chú thích /* */ và dấu phẩy ở cuối đều được chấp nhận.

Cấu hình khởi đầu nên dùng

Cho một ứng dụng hoặc script Node.js:

{
    "compilerOptions": {
        "rootDir": "./src",
        "outDir": "./dist",

        "target": "es2024",
        "lib": ["es2024"],
        "module": "nodenext",
        "types": ["node"],

        "strict": true,
        "noUncheckedIndexedAccess": true,
        "verbatimModuleSyntax": true,
        "skipLibCheck": true,
        "sourceMap": true
    },
    "include": ["src"]
}

Cấu hình này cần npm install --save-dev @types/node để có kiểu của Node.js. Với module: "nodenext", trường "type" trong package.json quyết định một tệp .ts là ES module ("type": "module") hay CommonJS ("type": "commonjs", giá trị mà npm init -y ghi vào). Hãy dùng "type": "module" cho dự án mới có dùng import và export.

Với code do một bundler như Vite, esbuild hay webpack build, bundler viết ra JavaScript còn tsc chỉ kiểm tra:

{
    "compilerOptions": {
        "target": "es2022",
        "lib": ["es2022", "dom"],
        "module": "preserve",
        "noEmit": true,

        "strict": true,
        "noUncheckedIndexedAccess": true,
        "verbatimModuleSyntax": true,
        "isolatedModules": true,
        "skipLibCheck": true,
        "jsx": "react-jsx"
    },
    "include": ["src"]
}

Các bộ khởi tạo của framework (Vite, Next.js, Angular) tự tạo tsconfig.json riêng. Hãy bắt đầu từ tệp đó thay vì thay thế nó.

Các tùy chọn quan trọng

Tùy chọnTác dụngMặc định trong TypeScript 7
targetPhiên bản JavaScript của đầu ra; cú pháp mới hơn được viết lại cho target cũes2025
libKiểu của các API dựng sẵn mà trình kiểm tra biết (Array.prototype.at, Map, document)Theo target, cộng thêm DOM
moduleLoại code module được xuất ra và quy tắc module được áp dụngesnext
moduleResolutionCách tìm đường dẫn import trên ổ đĩanodenext hoặc node16 khi đi với module tương ứng, còn lại là bundler
strictBật cả nhóm kiểm tra nghiêm ngặttrue
rootDirThư mục có cấu trúc được sao lại vào outDirThư mục chứa tsconfig.json
outDirNơi đặt các tệp .js (và .d.ts)Cạnh mỗi tệp nguồn
include / exclude / filesNhững tệp nào thuộc dự ánMọi tệp .ts trong thư mục
typesNhững package @types nào được nạp mà không cần import[], không có
noEmitChỉ kiểm tra, không ghi gìfalse
declarationGhi thêm tệp khai báo kiểu .d.tsfalse
sourceMapGhi tệp .js.map cho trình gỡ lỗifalse
esModuleInteropCho phép import x from "cjs-package" hoạt động với package CommonJStrue (không tắt được)
skipLibCheckBỏ qua kiểm tra kiểu các tệp .d.ts, kể cả trong node_modulesfalse

target và lib

target cho biết đầu ra phải chạy trên phiên bản JavaScript nào. Cú pháp mới hơn target sẽ được viết lại: với "target": "es2017", một class field hay ??= được chuyển thành code cũ hơn. Target thấp nhất mà TypeScript 7 chấp nhận là es2015 (es6); es5 đã bị loại bỏ.

target không bổ sung các API lúc chạy còn thiếu. Mô tả chúng là việc của lib, còn cung cấp chúng là việc của polyfill. lib cho trình kiểm tra biết những đối tượng và phương thức dựng sẵn nào tồn tại. Mặc định của nó đi theo target và có cả kiểu DOM của trình duyệt, nên document vẫn qua kiểm tra kiểu ngay cả trong dự án Node.js, trừ khi bạn tự đặt lib. Dùng một phương thức thuộc chuẩn mới hơn lib của bạn là lỗi biên dịch:

src/b.ts(1,24): error TS2550: Property 'toSorted' does not exist on type 'number[]'. Do you need to change your target library? Try changing the 'lib' compiler option to 'es2023' or later.

Các ví dụ chạy được trong tài liệu này dùng lib es2022, nên toSorted, Object.groupBy và các phương thức mới của Set không có trong đó.

module và moduleResolution

module có ba giá trị hợp lý cho code mới:

  • nodenext: cho code mà Node.js chạy. Mỗi tệp là ESM hay CommonJS tùy theo phần mở rộng (.mts, .cts) hoặc trường "type" của package.json gần nhất, đúng như cách Node quyết định. Import tương đối trong ES module cần phần mở rộng tệp, viết là .js dù mã nguồn là .ts: import { add } from "./math.js". moduleResolution tự động đi theo.
  • preserve: cho code do bundler xử lý. Các import được giữ nguyên như khi viết, và việc phân giải dùng quy tắc bundler, cho phép import không có phần mở rộng.
  • esnext: đầu ra ES module thuần, với cách phân giải bundler. Đây là mặc định khi không đặt module.

Một lỗi đầu tiên hay gặp với cấu hình của tsc --init đến từ việc npm init -y ghi "type": "commonjs":

src/main.ts(1,10): error TS1295: ECMAScript imports and exports cannot be written in a CommonJS file under 'verbatimModuleSyntax'.

Tệp dùng import, nhưng package.json lại nói dự án là CommonJS. Đổi "type" thành "module". Trang về module trình bày chi tiết về import và export.

strict và các tùy chọn kiểm tra

"strict": true bật cùng lúc một nhóm kiểm tra: noImplicitAny, strictNullChecks, strictFunctionTypes, strictBindCallApply, strictPropertyInitialization, strictBuiltinIteratorReturn, noImplicitThis và useUnknownInCatchVariables. Đây là mặc định trong TypeScript 7 và được bật trong mọi ví dụ chạy được ở đây. Code viết cho chế độ strict sẽ thu hẹp kiểu trước khi dùng một giá trị có thể không tồn tại:

Không có chú thích kiểu, kiểu của tham số không thể suy ra được, và noImplicitAny báo lỗi:

index.ts(2,17): error TS7006: Parameter 'n' implicitly has an 'any' type.

strict không gồm mọi kiểm tra hữu ích. Có hai tùy chọn đáng thêm vào là noUncheckedIndexedAccess (truy cập một phần tử mảng hay một index signature cho ra kiểu T | undefined) và exactOptionalPropertyTypes (không thể gán tường minh undefined cho một thuộc tính tùy chọn); tsc --init bật cả hai. Các kiểm tra về phong cách như noUnusedLocals, noImplicitReturns và noFallthroughCasesInSwitch mặc định đều tắt.

Những tệp nào: include, exclude, rootDir, outDir

Khi không có include hay files, dự án gồm mọi tệp .ts, .tsx và .d.ts trong thư mục và các thư mục con. node_modules luôn bị loại ra. outDir cũng bị loại ra, nhưng chỉ khi bạn chưa đặt exclude: một danh sách exclude tự đặt sẽ thay thế mặc định đó, nên hãy thêm thư mục đầu ra vào danh sách. include và exclude nhận mẫu glob:

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

exclude chỉ lọc những gì include đã tìm thấy. Một tệp được một tệp khác trong dự án import vẫn được biên dịch.

outDir là nơi đặt đầu ra, còn rootDir là phần cây mã nguồn được sao lại vào đó: với "rootDir": "./src", src/api/users.ts trở thành dist/api/users.js. Hãy đặt cả hai. rootDir mặc định là thư mục chứa tsconfig.json, nên nếu bạn chỉ đặt outDir trong khi mã nguồn nằm trong src, TypeScript 7 dừng lại với lỗi:

error TS5011: The common source directory of 'tsconfig.json' is './src'. The 'rootDir' setting must be explicitly set to this or another path to adjust your output's file layout.

Tùy chọn đầu ra: noEmit, declaration, sourceMap

  • "noEmit": true biến tsc thành một trình kiểm tra kiểu thuần túy. Dùng nó khi một công cụ khác (bundler, tsx, tính năng bỏ kiểu của Node.js) tạo ra JavaScript.
  • "declaration": true ghi một tệp .d.ts cho mỗi module, chỉ có kiểu mà không có code. Thư viện cần tùy chọn này để người dùng có được kiểu. declarationMap thêm các map để "go to definition" dẫn tới mã nguồn .ts.
  • "sourceMap": true ghi các tệp .js.map để trình gỡ lỗi và stack trace trỏ đúng dòng trong tệp .ts.
  • "noEmitOnError": true không ghi gì khi còn lỗi kiểu. Không có nó, tsc vẫn báo lỗi và vẫn ghi ra JavaScript.

types, esModuleInterop và skipLibCheck

types liệt kê các package @types được nạp toàn cục, không cần import. Mặc định của TypeScript 7 là danh sách rỗng, nên sau npm install --save-dev @types/node bạn còn phải thêm "types": ["node"]; nếu không, process và require vẫn là tên không xác định (error TS2591: Cannot find name 'process'). Các trình chạy test có hàm toàn cục, như describe của Jest, cũng được thêm vào danh sách này.

esModuleInterop giúp default import từ package CommonJS hoạt động (import express from "express"). Tùy chọn này luôn bật trong TypeScript 7, và đặt nó thành false là lỗi.

skipLibCheck bỏ qua việc kiểm tra kiểu các tệp khai báo. Nó giúp build nhanh hơn và tránh những lỗi bên trong node_modules mà bạn không sửa được, đổi lại bạn sẽ không phát hiện xung đột giữa kiểu của hai thư viện. Hầu hết dự án đều bật nó.

Dùng chung thiết lập với extends

extends nạp một cấu hình khác và cho phép tệp này ghi đè một phần của nó:

{
    "extends": "./tsconfig.base.json",
    "compilerOptions": {
        "rootDir": "./src",
        "outDir": "./dist"
    },
    "include": ["src"]
}

compilerOptions được gộp theo từng tùy chọn, còn include, exclude và files từ tệp gốc sẽ bị thay thế chứ không gộp nếu tệp này có đặt chúng. Đường dẫn trong tệp gốc được phân giải tương đối theo chính tệp gốc. extends cũng nhận một package: dự án @tsconfig/bases phát hành một cấu hình cho mỗi môi trường, ví dụ npm install --save-dev @tsconfig/node24 rồi "extends": "@tsconfig/node24/tsconfig.json".

Để xem thiết lập cuối cùng sau khi đã áp dụng mọi extends và giá trị mặc định, chạy:

npx tsc --showConfig

Các tùy chọn bị loại bỏ trong TypeScript 7

TypeScript 6 đánh dấu lỗi thời các thiết lập này và TypeScript 7 đã loại bỏ chúng. Một cấu hình vẫn dùng một trong số đó sẽ thất bại với error TS5108 hoặc TS5102, ví dụ Option 'baseUrl' has been removed. Please remove it from your configuration.

Thiết lập bị loại bỏDùng thay thế
"target": "es5"es2015 trở lên
"moduleResolution": "node" (node10) hoặc "classic"nodenext hoặc bundler
"module": "amd", "umd", "system", "none"nodenext, esnext hoặc preserve, và một bundler cho các định dạng khác
baseUrlCác mục paths tương đối theo tệp tsconfig
outFileMột bundler
downlevelIterationKhông cần gì: các target từ es2015 trở lên hỗ trợ sẵn việc lặp
"esModuleInterop": false, "allowSyntheticDefaultImports": false, "alwaysStrict": falseXóa dòng đó; các tùy chọn này luôn bật

Trang TypeScript 7 liệt kê các thay đổi khác trong phiên bản đó, kể cả những giá trị mặc định mới mà bảng này không đề cập.

Câu hỏi thường gặp

tsconfig.json là gì?

Đây là tệp cấu hình của một dự án TypeScript. Sự có mặt của tệp này đánh dấu thư mục là gốc của dự án, compilerOptions quy định cách trình biên dịch kiểm tra và xuất code, còn include, exclude hoặc files cho biết những tệp nào thuộc dự án. Chạy tsc không kèm đối số sẽ đọc tệp này.

Làm sao để tạo tệp tsconfig.json?

Chạy npx tsc --init trong thư mục dự án (khi đã cài TypeScript). Lệnh này tạo một tsconfig.json với các thiết lập nên dùng và một chú thích cho từng tùy chọn. Bạn cũng có thể tự viết tệp này; {} là một tsconfig hợp lệ, dùng mọi giá trị mặc định.

Nên đặt target trong tsconfig là gì?

Phiên bản JavaScript cũ nhất mà code của bạn phải chạy được. Với Node.js hiện nay, es2022 trở lên là an toàn; mặc định của TypeScript 7 là es2025. target quyết định cú pháp mới nào được viết lại cho các engine cũ, đồng thời chọn lib mặc định, tức tập API dựng sẵn mà trình kiểm tra kiểu biết đến.

module và moduleResolution khác nhau thế nào?

module quyết định loại code module mà trình biên dịch viết ra (ES import/export hay CommonJS require) và quy tắc module nào được áp dụng. moduleResolution quyết định cách tìm một đường dẫn import như "./utils.js" hay "lodash" trên ổ đĩa. Dùng nodenext cho code mà Node.js chạy, và module: "preserve" (kéo theo cách phân giải bundler) cho code do bundler xử lý.

tsconfig.json có chứa chú thích được không?

Được. Trình biên dịch đọc tệp này như JSON có chú thích: chú thích // và /* */ cùng dấu phẩy ở cuối đều hợp lệ, vì vậy tsc --init tạo ra một tệp đầy những tùy chọn đã bị chú thích lại. Những công cụ khác đọc tệp như JSON nghiêm ngặt có thể báo lỗi vì chúng.

Hình minh họa các ngôn ngữ lập trình của Coddy

Học lập trình cùng Coddy

Bắt đầu