Bối cảnh vấn đềGần đây tôi đã chuyển đổi một codebase React quy mô trung bình—khoảng 45.000 dòng code—từ Webpack sang Vite. Quá trình chuyển đổi diễn ra khá suôn sẻ cho đến khi tôi động đến các 'barrel files'. Những file index.ts này tổng hợp các export của chúng tôi, nhưng chúng khiến Vite dev server gặp lỗi. Console ngay lập tức báo lỗi này:
Re-exporting a type when the '--isolatedModules' flag is provided requires using 'export type'.
Điều này xảy ra vì Vite sử dụng esbuild để chuyển đổi mã (transpilation). Không giống như trình biên dịch TypeScript tiêu chuẩn (tsc), vốn phân tích toàn bộ đồ thị dự án, esbuild xử lý từng file một cách hoàn toàn cô lập. Nó cực kỳ nhanh—thường nhanh hơn từ 10 đến 100 lần so với tsc—nhưng nó có một điểm yếu. Nó không thể biết liệu một tên được export là một hằng số JavaScript hay một interface TypeScript cần được loại bỏ trong quá trình build.
Quá trình gỡ lỗiGốc rễ của vấn đề nằm ở file tsconfig.json của bạn. Nếu đang sử dụng các công cụ build hiện đại, có thể bạn đã bật thiết lập này:
{
"compilerOptions": {
"isolatedModules": true
}
}
Khi isolatedModules được kích hoạt, TypeScript ngăn bạn sử dụng các pattern mà một trình transpiler đơn file không thể xử lý. Re-export một type bằng cú pháp tiêu chuẩn là một trong những pattern bị cấm đó. Hãy xem ví dụ phổ biến này trong file src/types/index.ts:
// src/types/user.ts
export interface User {
id: string;
name: string;
}
// src/types/index.ts
export { User } from './user'; // <-- Dòng này gây ra lỗi
Trình transpiler thấy export { User } và bị bối rối. Nó không biết nên tạo mã cho một đối tượng runtime hay xóa hoàn toàn dòng đó vì User chỉ là một type.
Các giải pháp### 1. Sử dụng 'export type' rõ ràngCách khắc phục đơn giản nhất là cho trình biên dịch biết chính xác bạn đang làm gì. Cú pháp này được giới thiệu trong TypeScript 3.8 và đánh dấu rõ ràng export này để loại bỏ khi build.
// Thay đổi từ:
export { User } from './user';
// Thành:
export type { User } from './user';
2. Export hỗn hợp (TypeScript 4.5+)Nếu bạn cần export một component và type tương ứng của nó từ cùng một file, bạn không cần hai dòng riêng biệt. Các công cụ sửa đổi type nội dòng (inline type modifiers) giúp mã sạch hơn nhiều.
// src/components/index.ts
export {
Button,
type ButtonProps
} from './Button';
3. Tách biệt import và exportĐối với các nhóm vẫn đang dùng phiên bản TypeScript cũ hơn hoặc những người thích phong cách tường minh hơn, bạn có thể chia nhỏ thao tác. Import type trước, sau đó export nó riêng biệt.
import { User } from './user';
export type { User };
Xác minhĐừng chỉ dựa vào Vite HMR (Hot Module Replacement) để biết lỗi đã được sửa. Tôi luôn chạy kiểm tra type thủ công trong terminal để chắc chắn:
npx tsc --noEmit
Flag --noEmit rất hoàn hảo trong trường hợp này. Nó thực hiện kiểm tra type toàn diện mà không tạo ra bất kỳ file .js nào, đây chính xác là điều bạn muốn khi Vite hoặc esbuild đang xử lý việc đóng gói (bundling) thực tế.

