- Đọc và viết được
tsconfig.jsonđầy đủ — hiểu rõcompilerOptions,include,exclude,references. - Chọn đúng
target/module/moduleResolutioncho 3 ngữ cảnh: Node CLI, SPA browser, library publish. - Hiểu sâu strict mode — biết "strict": true bật bao nhiêu flag và mỗi flag bảo vệ gì.
- Viết được file
.d.tsđơn giản cho thư viện không có type. - Khai báo ambient module và mở rộng
Window,NodeJS.ProcessEnv, ... - Dựng monorepo với project references — incremental build, isolate type-check.
- Master pattern thực tế: typed env (zod), typed fetch wrapper, repository pattern.
- Debug TypeScript với source map và VS Code.
Khác với Java/C# compile sang bytecode chạy thẳng, TypeScript chỉ làm 2 việc: (1) kiểm tra type, (2) strip annotation type ra để xuất JavaScript thuần. Output đó mới được Node/browser chạy. Vì vậy mọi đặc tả runtime (target ES version, module format, paths…) phải khớp với môi trường chạy. Chương này dạy bạn cách khớp đúng.
1. tsconfig.json — giải phẫu file config
tsconfig.json là file JSON đặt ở root dự án. Khi gõ tsc không tham số,
compiler tự tìm tsconfig.json ở thư mục hiện tại (rồi đi lên cha) và đọc config từ đó.
4 phần chính:
| Field | Mục đích | Bắt buộc |
|---|---|---|
compilerOptions | Cấu hình compiler: target, module, strict, paths… | Có (gần như) |
include | Glob các file/thư mục cần compile | Không (default tất cả .ts) |
exclude | Loại trừ file khỏi compile (mặc định: node_modules, bower_components, jspm_packages, outDir) | Không |
references | Liên kết tới các sub-project (monorepo) | Không |
extends | Kế thừa từ tsconfig khác (vd: @tsconfig/node20) | Không |
files | Liệt kê file cụ thể (thay vì glob) | Không |
Một tsconfig.json tối thiểu cho project mới:
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"outDir": "dist"
},
"include": ["src"],
"exclude": ["node_modules", "dist"]
}
Chạy npx tsc --init để TypeScript sinh ra tsconfig.json mặc định có
comment giải thích từng option. Tốt cho người mới đọc trước, sau đó xoá các option không dùng.
Hoặc kế thừa template chính thức: npm i -D @tsconfig/node20 rồi
"extends": "@tsconfig/node20/tsconfig.json".
2. target và module — chọn đúng phiên bản
Hai option quan trọng nhất, hay bị nhầm. target = phiên bản JavaScript output;
module = format module (CommonJS, ESM, …).
2.1. target — phiên bản ECMAScript output
TypeScript sẽ down-level các tính năng JS hiện đại về phiên bản này. Ví dụ target: "ES5"
khiến optional chaining ?. được biên dịch thành chuỗi &&.
| target | Khi nào dùng |
|---|---|
ES5 | Hỗ trợ IE11 (đã chết). Tránh. |
ES2015 | Browser cũ. Hiếm dùng 2026. |
ES2020 | Node 14+, browser modern. Có optional chaining, nullish coalescing. |
ES2022 | Sweet spot 2026. Có class fields, top-level await, at(). |
ESNext | Bleeding edge. Output không down-level. Phù hợp library bundle bởi consumer. |
2.2. module — định dạng module output
| module | Output dạng | Khi dùng |
|---|---|---|
CommonJS | module.exports + require() | Node legacy, package CJS |
ESNext | import / export giữ nguyên | Bundler (Vite, Webpack) tự xử lý |
NodeNext | Mix CJS/ESM theo file extension | Node ESM thuần (package.json "type":"module") |
UMD / AMD / SystemJS | Format legacy | Hầu như không dùng nữa |
Đặt target: "ES2022" nhưng triển khai code lên Node 12 → top-level await không có,
crash. Đặt module: "CommonJS" nhưng dùng import.meta.url → lỗi.
Luôn check matrix: target ↔ runtime version ↔ module format ↔ package.json
"type".
3. Strict mode — luôn bật
Cờ "strict": true bật 8+ flag con cùng lúc. Đây là cách
TypeScript thực sự bảo vệ bạn khỏi bug. Luôn bật strict cho project mới.
Cho project cũ migrate dần thì bật từng flag.
| Flag | Tác dụng |
|---|---|
strictNullChecks | Phân biệt null/undefined với type khác. Quan trọng nhất. |
noImplicitAny | Bắt buộc khai báo type, không cho ngầm any. |
strictFunctionTypes | Check contravariant tham số function chặt chẽ. |
strictBindCallApply | .bind/.call/.apply check type tham số. |
strictPropertyInitialization | Class property phải init trong constructor hoặc khai báo. |
alwaysStrict | Output có 'use strict' (parse module ở strict mode). |
noImplicitThis | Cấm this kiểu any. |
useUnknownInCatchVariables | catch (e) ngầm unknown, không phải any. |
Các flag bổ sung khuyên bật thêm (không trong strict):
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true, // arr[i] có thể undefined
"noImplicitOverride": true, // override method phải có keyword override
"noFallthroughCasesInSwitch": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"exactOptionalPropertyTypes": true // { x?: number } khác { x: number | undefined }
}
}
4. Module resolution — cách TS tìm file
Khi bạn viết import x from './foo' hay import lodash from 'lodash',
TypeScript phải tìm file thực tương ứng. Cách tìm phụ thuộc
moduleResolution:
| moduleResolution | Hành vi | Khi dùng |
|---|---|---|
classic | Legacy TS 1.x. Đừng dùng. | — |
node | Bắt chước Node CommonJS: ./foo → ./foo.ts, ./foo/index.ts, … | Node CJS classic, project cũ. |
node16 / nodenext | Node ESM: bắt buộc .js trong import (cả khi file là .ts!) | Node ESM với "type":"module". |
bundler | Bundler-friendly: không cần extension, hỗ trợ paths, condition exports. | Mặc định cho Vite/Webpack/esbuild. |
Với moduleResolution: "nodenext", dù file của bạn là foo.ts,
bạn vẫn phải import bằng import { x } from './foo.js'. Lý do: sau khi compile
ra foo.js, Node ESM yêu cầu extension đầy đủ. TypeScript "nói trước" để code
output runnable.
5. Output options — controls cho file sinh ra
| Option | Mục đích |
|---|---|
outDir | Thư mục chứa output. Mặc định cùng folder với file .ts. |
rootDir | Thư mục gốc của source. Cấu trúc trong rootDir được giữ ở outDir. |
declaration | Sinh kèm file .d.ts. Bắt buộc nếu publish library. |
declarationMap | Sinh .d.ts.map để "Go to Definition" nhảy về file .ts gốc. |
sourceMap | Sinh .js.map — debug breakpoint trên .ts. |
removeComments | Loại bỏ comment trong output. |
importHelpers | Dùng tslib để chia sẻ helper (giảm size khi nhiều file). |
noEmit | Chỉ type-check, không xuất file. Hữu ích khi đã có Vite/esbuild compile. |
incremental | Lưu cache .tsbuildinfo, build lần sau nhanh hơn. |
6. Path mapping — import alias đẹp
Thay vì import Button from '../../../components/Button', ta muốn
import Button from '@/components/Button'. Cấu hình:
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@utils/*": ["src/utils/*"],
"@types": ["src/types/index.ts"]
}
}
}
Code sau khi cấu hình:
// trước
import Button from '../../../components/Button';
// sau
import Button from '@/components/Button';
tsc hiểu path mapping khi type-check, nhưng không rewrite
import lúc emit. Khi runtime, Node sẽ vẫn cố tìm package tên "@". Bạn cần một trong các giải pháp:
- Vite: cấu hình thêm
resolve.aliastrongvite.config.ts(Vite có pluginvite-tsconfig-pathsđọc tự động). - esbuild/tsup: dùng plugin alias hoặc
esbuild-plugin-tsconfig-paths. - Node thuần (tsx/ts-node):
tsxhỗ trợ tự động; vớits-nodecầntsconfig-paths/register.
7. .d.ts — file khai báo type
File .d.ts chứa chỉ khai báo type, không có code chạy. Mục đích:
mô tả "shape" của 1 module JS thuần để TS hiểu type khi import.
3 nguồn .d.ts:
- Đi kèm package: nhiều thư viện modern (zod, react, vue) ship sẵn file
.d.tstrong npm package. - DefinitelyTyped — kho cộng đồng. Cài qua
npm i -D @types/lodash,@types/node,@types/express, … - Tự viết — khi không có 2 nguồn trên.
Ví dụ .d.ts tự viết cho thư viện hypothesi string-strip-html:
declare module 'string-strip-html' {
export function stripHtml(
html: string,
opts?: { onlyStripTags?: string[] }
): { result: string };
}
.d.ts ở đâu?
Tạo thư mục src/types/ hoặc types/ ở root. Đảm bảo nằm trong
include của tsconfig.json (hoặc khai báo thêm
"typeRoots"). TypeScript sẽ tự load các file .d.ts ở đó.
8. Ambient declaration — khai báo toàn cục
Mở rộng kiểu của thứ "có sẵn" như Window, globalThis, hay process.env.
8.1. Mở rộng Window / globalThis
declare global {
interface Window {
myAnalytics: {
track(event: string): void;
};
}
}
// Cần dòng export để file thành module — declare global mới hiệu lực
export {};
Lưu ý dòng export {}; ở cuối — nếu thiếu, file bị coi là script global, declare global không hoạt động.
8.2. Type hoá process.env
declare global {
namespace NodeJS {
interface ProcessEnv {
NODE_ENV: 'development' | 'production' | 'test';
DATABASE_URL: string;
PORT?: string;
}
}
}
export {};
Cách này khai man — chỉ tell TS, không validate runtime. Pattern an toàn hơn: dùng
zod validate process.env rồi infer type (xem §13).
8.3. declare module cho CSS, ảnh, file lạ
declare module '*.css';
declare module '*.svg' {
const src: string;
export default src;
}
declare module '*.md?raw' {
const content: string;
export default content;
}
9. Project references — Monorepo nhiều package
Khi dự án có nhiều package cùng repo (vd: packages/utils,
packages/app), bạn không muốn 1 file tsconfig.json khổng lồ. Mỗi
package có tsconfig.json riêng, link với nhau qua references.
Cấu trúc thư mục mẫu:
monorepo/
├─ tsconfig.json // solution-style root: chỉ liệt kê references
├─ packages/
│ ├─ utils/
│ │ ├─ tsconfig.json
│ │ └─ src/index.ts
│ └─ app/
│ ├─ tsconfig.json
│ └─ src/index.ts
└─ package.json
Root tsconfig.json:
{
"files": [],
"references": [
{ "path": "./packages/utils" },
{ "path": "./packages/app" }
]
}
packages/utils/tsconfig.json:
{
"compilerOptions": {
"composite": true, // bắt buộc khi được reference
"declaration": true,
"outDir": "dist",
"rootDir": "src",
"target": "ES2022",
"module": "ESNext",
"strict": true
},
"include": ["src"]
}
packages/app/tsconfig.json:
{
"extends": "../utils/tsconfig.json",
"compilerOptions": {
"outDir": "dist",
"rootDir": "src"
},
"include": ["src"],
"references": [{ "path": "../utils" }]
}
Build:
$ tsc -b # build tất cả references theo thứ tự
$ tsc -b --clean # xoá tất cả output
$ tsc -b --watch # incremental + watch
- Incremental build: thay đổi
utilschỉ rebuildutils+ downstream phụ thuộc, không rebuild toàn bộ. - Isolate: package có config riêng (vd target khác).
- Type-check song song trên CI.
10. Setup case 1 — Node CLI
Mục tiêu: viết script Node bằng TypeScript, chạy nhanh trong dev, build sang JS để deploy.
Bước 1: khởi tạo
$ mkdir my-cli && cd my-cli
$ npm init -y
$ npm i -D typescript tsx @types/node
$ npx tsc --init
Bước 2: tsconfig.json
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"outDir": "dist",
"rootDir": "src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"declaration": false,
"sourceMap": true
},
"include": ["src"]
}
Trong package.json thêm "type": "module" và scripts:
{
"type": "module",
"scripts": {
"dev": "tsx watch src/index.ts",
"start": "tsx src/index.ts",
"build": "tsc",
"check": "tsc --noEmit"
}
}
Bước 3: viết src/index.ts
import { readdir } from 'node:fs/promises';
async function main(): Promise<void> {
const dir = process.argv[2] ?? '.';
const entries = await readdir(dir);
console.log(`${entries.length} file/folder trong ${dir}`);
}
main();
Chạy dev: npm run dev. Build deploy: npm run build → node dist/index.js.
11. Setup case 2 — SPA browser với Vite
Vite tự xử lý TS — không cần chạy tsc trong dev (chỉ tsc --noEmit để type-check).
$ npm create vite@latest my-app -- --template vanilla-ts
$ cd my-app && npm install
$ npm run dev
tsconfig.json Vite sinh ra (đơn giản hoá):
{
"compilerOptions": {
"target": "ES2022",
"useDefineForClassFields": true,
"module": "ESNext",
"moduleResolution": "bundler",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noEmit": true,
"isolatedModules": true,
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
},
"include": ["src"]
}
Để Vite resolve @/*:
import { defineConfig } from 'vite';
import path from 'node:path';
export default defineConfig({
resolve: {
alias: {
'@': path.resolve(__dirname, 'src')
}
}
});
Hoặc nhanh hơn: cài vite-tsconfig-paths, plugin tự đọc paths trong
tsconfig.json.
12. Setup case 3 — Publish library
Library cần xuất 3 thứ: CJS (cho Node CJS cũ), ESM (cho
Node modern + bundler), .d.ts (cho TS user). Công cụ đề xuất:
tsup (wrap esbuild, zero-config).
$ npm i -D tsup typescript
tsup.config.ts:
import { defineConfig } from 'tsup';
export default defineConfig({
entry: ['src/index.ts'],
format: ['cjs', 'esm'],
dts: true,
sourcemap: true,
clean: true,
target: 'es2022'
});
package.json với exports map:
{
"name": "my-lib",
"version": "1.0.0",
"main": "./dist/index.cjs",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"require": "./dist/index.cjs"
}
},
"files": ["dist"],
"scripts": {
"build": "tsup",
"check": "tsc --noEmit"
}
}
Build: npm run build. Publish: npm publish. User cài về sẽ thấy
autocomplete đầy đủ.
13. Patterns thực tế
13.1. Typed env với zod
Validate process.env ngay khi app start. Sai → throw sớm, đỡ debug runtime mơ hồ.
import { z } from 'zod';
const EnvSchema = z.object({
NODE_ENV: z.enum(['development', 'production', 'test']),
DATABASE_URL: z.string().url(),
PORT: z.coerce.number().int().positive().default(3000)
});
export const env = EnvSchema.parse(process.env);
// env có type: { NODE_ENV: 'development' | ..., DATABASE_URL: string, PORT: number }
13.2. Typed fetch wrapper
export class ApiError extends Error {
constructor(public readonly status: number, message: string) {
super(message);
this.name = 'ApiError';
}
}
export async function api<T>(
url: string,
init?: RequestInit
): Promise<T> {
const res = await fetch(url, init);
if (!res.ok) {
throw new ApiError(res.status, `HTTP ${res.status}`);
}
return res.json() as Promise<T>;
}
// Usage:
interface User { id: number; name: string; }
const u = await api<User>('/api/users/1');
// u tự động có type User
Cảnh báo: as Promise<T> là "tin tưởng server trả đúng type". Production
nên kết hợp zod: UserSchema.parse(await res.json()) — runtime validation
luôn.
13.3. Repository pattern
export interface User { id: number; email: string; }
export interface UserRepository {
findById(id: number): Promise<User | null>;
create(data: Omit<User, 'id'>): Promise<User>;
}
// Triển khai 1: Sqlite
export class SqliteUserRepo implements UserRepository {
async findById(id: number) { /* ... */ return null; }
async create(data: Omit<User, 'id'>) { /* ... */ return { id: 1, ...data }; }
}
// Triển khai 2: In-memory (cho test)
export class MockUserRepo implements UserRepository {
private users = new Map<number, User>();
async findById(id: number) { return this.users.get(id) ?? null; }
async create(data: Omit<User, 'id'>) {
const id = this.users.size + 1;
const user = { id, ...data };
this.users.set(id, user);
return user;
}
}
Caller chỉ phụ thuộc vào UserRepository interface — đổi triển khai mà không phải
sửa logic. Đây là Dependency Inversion Principle (Pillar OOP) áp dụng vào
TypeScript thực tế.
14. Debug TypeScript
3 cách phổ biến:
console.logngon. Vớitsx, log hiện ngay file.tsgốc (nhờ source map nội bộ).- VS Code debugger với source map. Bật
"sourceMap": truetrongtsconfig.json, tạo launch config:
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Debug TS (tsx)",
"runtimeExecutable": "tsx",
"args": ["${workspaceFolder}/src/index.ts"],
"skipFiles": ["<node_internals>/**"]
}
]
}
Đặt breakpoint trực tiếp trên file .ts, F5 chạy. Variable inspector hiện đúng tên biến TS.
- Chrome DevTools cho browser: Vite tự bật source map dev. Mở DevTools → tab Sources → tìm file
.tstrong tree, đặt breakpoint như JS thường.
Bài tập
Bài 1 — TS Node CLI từ đầu
Tạo project Node CLI mới. Cài tsx + typescript + vitest.
- Viết
src/count-files.tsnhận đường dẫn từprocess.argv[2], dùngnode:fs/promisesđếm file (không tính folder con). - Script
npm run devdùngtsxchạy trực tiếp. - Script
npm run buildcompiletscradist/. Verify chạy được bằngnode dist/count-files.js .. - Viết test
vitestđơn giản: tạo folder tạm, ghi 3 file, gọi hàmcountFiles, expect3.
Gợi ý cấu trúc
// src/count-files.ts
import { readdir, stat } from 'node:fs/promises';
import { join } from 'node:path';
export async function countFiles(dir: string): Promise<number> {
const entries = await readdir(dir);
let count = 0;
for (const e of entries) {
const s = await stat(join(dir, e));
if (s.isFile()) count++;
}
return count;
}
if (import.meta.url === `file://${process.argv[1]}`) {
const dir = process.argv[2] ?? '.';
countFiles(dir).then(n => console.log(n));
}
Bài 2 — Vite + path alias
Khởi tạo Vite + framework yêu thích (vanilla-ts / vue-ts / svelte-ts / react-ts).
Sửa tsconfig.json:
- Bật
"strict": true+"noUncheckedIndexedAccess": true. - Thêm
"baseUrl": "."và"paths": { "@/*": ["src/*"] }. - Cập nhật
vite.config.tsvớiresolve.aliastương ứng (hoặc càivite-tsconfig-paths). - Tạo
src/components/Hello.tsexport 1 function. Trongsrc/main.tsimport bằng@/components/Hello. Verify hot reload hoạt động.
Tips
Nếu hot-reload báo lỗi "Cannot find module '@/components/Hello'", có thể bạn quên config alias bên Vite — TS only nhận paths cho type-check, runtime cần Vite biết alias.
Bài 3 — Tự viết file .d.ts
Cài 1 npm package không có type. Ví dụ phiên bản cũ của string-strip-html@4
hoặc 1 package nhỏ tự chọn. Khi import, TS sẽ báo "Could not find a declaration file".
- Tạo
src/types/<ten-pkg>.d.ts. - Viết
declare module '...'với type tối thiểu (signature 1-2 hàm bạn thật sự dùng). - Verify autocomplete hoạt động trong VS Code.
- Đảm bảo file
.d.tsnằm trongincludecủatsconfig.json.
Mẫu
declare module 'string-strip-html' {
export function stripHtml(
html: string
): { result: string };
}
Bài 4 — Typed fetch wrapper
Implement hàm api<T>(url, init?):
- Generic
Tcho shape data trả về. - Throw
ApiErrornếu!res.ok, chứastatus,message. - Test với
https://jsonplaceholder.typicode.com/users/1. Khai báointerface User, gọiapi<User>(...), in tên. - Bonus: thêm option timeout (dùng
AbortController). - Bonus 2: kết hợp
zodđể runtime-validate response.
Skeleton
export class ApiError extends Error {
constructor(public status: number, msg: string) { super(msg); }
}
export async function api<T>(
url: string,
init: RequestInit & { timeoutMs?: number } = {}
): Promise<T> {
const { timeoutMs = 10000, ...rest } = init;
const ctrl = new AbortController();
const t = setTimeout(() => ctrl.abort(), timeoutMs);
try {
const res = await fetch(url, { ...rest, signal: ctrl.signal });
if (!res.ok) throw new ApiError(res.status, res.statusText);
return res.json() as Promise<T>;
} finally {
clearTimeout(t);
}
}
Bài 5 — Mini monorepo với project references
Tạo cấu trúc:
my-mono/
├─ tsconfig.json
├─ packages/
│ ├─ utils/ (export function add)
│ └─ app/ (import add từ utils, gọi log)
- Mỗi package có
tsconfig.jsonriêng,composite: trueở utils. - Root
tsconfig.jsonchỉ chứareferences. app/tsconfig.jsoncóreferencestrỏ tới../utils.- Build bằng
tsc -b. Kiểm tra.tsbuildinfođược tạo. - Edit
utils/src/index.ts→ rebuild → confirm chỉutilsvàappbị rebuild (chứ không phải toàn bộ).
Tips
Import bên app: import { add } from '../../utils/src/index' sẽ chạy, nhưng
xấu. Cách "đẹp": dùng workspace của npm/pnpm/yarn,
rồi import bằng tên package (import { add } from '@my-mono/utils'). Đây là
bước nâng cao — không bắt buộc trong bài này.
Quiz
"strict": true trong tsconfig bật những flag con nào?
Xem đáp án
8 flag: strictNullChecks, noImplicitAny, strictFunctionTypes, strictBindCallApply, strictPropertyInitialization, alwaysStrict, noImplicitThis, useUnknownInCatchVariables. Đây không phải set cố định mãi — TypeScript có thể thêm flag mới vào strict ở phiên bản sau (vd useUnknownInCatchVariables được thêm ở TS 4.4).
Khác nhau giữa tsx và ts-node? Nên dùng cái nào?
Xem đáp án
tsx dùng esbuild bên dưới — chạy rất nhanh nhưng chỉ strip type,
không type-check. ts-node gọi tsc thật — chậm hơn nhưng type-check khi chạy.
Khuyến nghị 2026: tsx cho dev (nhanh), tsc --noEmit riêng để type-check
trên CI hoặc pre-commit. ts-node hiện vẫn dùng nhưng không còn là default.
"moduleResolution": "bundler" dùng khi nào?
Xem đáp án
Khi dự án của bạn được biên dịch/bundle bởi bundler hiện đại (Vite,
esbuild, Webpack, Rollup) chứ không phải Node ESM thuần. Bundler tự xử lý
extension .js, conditional exports, path mapping… nên bundler
cho phép import "loose": import x from './foo' không cần .js.
Khi nào không dùng? Code chạy thẳng Node ESM (không qua bundler) → dùng
nodenext để TS bắt bạn viết extension đầy đủ.
File .d.ts có chứa logic JavaScript không?
Xem đáp án
Không. File .d.ts chỉ chứa khai báo:
declare, interface, type, export
signature… Không có thân hàm, không có console.log, không có code chạy.
Nếu thử viết export function foo() { return 1; } trong .d.ts,
TS sẽ báo lỗi "An implementation cannot be declared in ambient contexts".
Mặc định tsconfig.json không có "include" thì TS compile file nào?
Xem đáp án
Tất cả file .ts, .tsx, .d.ts (và .js/.jsx
nếu bật allowJs) trong thư mục chứa tsconfig.json và mọi thư mục con —
trừ những cái khớp exclude (mặc định node_modules,
bower_components, jspm_packages, và outDir).
Vì sao project Node luôn cần cài @types/node?
Xem đáp án
Node.js là binary C++, không có source TypeScript. Các API built-in (fs,
path, http, process, Buffer…) cần
khai báo type bổ sung từ ngoài. Cộng đồng cung cấp qua DefinitelyTyped,
publish thành package @types/node. Cài về và TS tự dùng (do
typeRoots mặc định bao gồm node_modules/@types).
Project references giải quyết vấn đề gì?
Xem đáp án
3 vấn đề trong monorepo nhiều package:
- Incremental build:
tsc -bchỉ rebuild package đã đổi và downstream phụ thuộc, không scan lại toàn bộ. - Type-check song song: mỗi package isolate, có thể chạy song song trên CI.
- Config riêng: mỗi package có target/lib/strict riêng nếu cần (vd: package frontend cần DOM, package CLI không cần).
Cần đặt "composite": true ở package được reference. Build qua
tsc -b thay vì tsc thường.
Tổng kết
Sau chương 12 bạn nên đã master:
- tsconfig.json anatomy — 4 phần chính, biết cấu hình cho từng loại dự án.
- target / module / moduleResolution — chọn đúng matrix khớp runtime.
- Strict mode — bật 8+ flag, biết mỗi flag bảo vệ gì.
- Path mapping —
@/*alias, nhớ cấu hình thêm phía bundler. - .d.ts — đọc, viết, đặt đúng vị trí.
declare modulevàdeclare global. - Project references — monorepo nhiều package, build incremental.
- 3 setup cases — Node CLI (tsx + tsc), Vite SPA, Library publish (tsup).
- Patterns thực tế — zod env, typed fetch, repository pattern.
- Debug TS — source map + VS Code launch config.
Kết nối
- Chương 9 (TS Types) — các type primitive và composite bạn đang viết trong
.d.ts. - Chương 10 (TS Generics) — generic
api<T>,UserRepositorydùng trong patterns ở §13. - Chương 11 (TS Advanced) — utility types, mapped types, conditional types tỏ ra hữu dụng khi viết
.d.ts. - Chương 8 (Modules) — module resolution chính là cầu nối: TS map import path → file thực, hệt như Node module resolution.
- Pillar OOP — Dependency Inversion — repository pattern ở §13.3 là DIP trực tiếp.
- Dart Phase 2 — Dart không có
tsconfig.json;pubspec.yamltương đươngpackage.json+ một phầntsconfig.json(target SDK), nhưng Dart compile native nên không có concept "moduleResolution".
12 chương, ~10.000 dòng kiến thức. Bạn đã đi từ "Hello JavaScript" qua biến/coercion, functions/closures, objects/prototype, arrays/iterables, async/await, error handling, modules, đến TypeScript đầy đủ với compiler và tooling thực chiến. Đây là nền tảng đủ mạnh để bạn:
- Đọc hiểu mọi codebase JS/TS modern (Next.js, NestJS, Vue, Svelte, …).
- Đóng góp pull request vào dự án open-source TypeScript.
- Phỏng vấn vị trí Frontend / Fullstack / Node backend tự tin.
- Tiếp tục học framework cụ thể mà không bị tắc ở "syntax JS lạ".
Bước tiếp theo đề xuất: chuyển sang sub-pillar Python — ngôn ngữ thứ hai cực kỳ giá trị (data, scripting, AI/ML, backend). Học song song JS/TS ↔ Python sẽ giúp bạn thấy rõ trade-off design của từng ngôn ngữ.
Hoặc tiếp tục đào sâu hệ JS: học framework cụ thể (Next.js, NestJS), thư viện type-heavy
như tRPC, Effect, hoặc đọc source code TypeScript compiler để
hiểu type-checking phía sau.