Lỗi Gặp Phải
Bạn viết một default import hoàn toàn chuẩn mực nhưng TypeScript lại phàn nàn ngay lập tức:
Module 'express' can only be default-imported using the 'allowSyntheticDefaultImports' flag
Module 'react' can only be default-imported using the 'allowSyntheticDefaultImports' flag
Module 'lodash' can only be default-imported using the 'allowSyntheticDefaultImports' flag
Lỗi này thường xuất hiện sau khi thêm một package npm mới, siết chặt cấu hình TypeScript, hoặc khi chuyển đổi một dự án JS hiện tại sang TypeScript.
Nguyên Nhân
Các package CommonJS cũ — điển hình như express, lodash, moment — không có default export kiểu ES module thực sự. Chúng xuất mọi thứ qua module.exports = .... TypeScript nhận ra sự không tương thích này và từ chối cho phép bạn viết:
import express from 'express'; // ❌ Lỗi nếu không bật flag
Flag allowSyntheticDefaultImports là cách TypeScript nói rằng: "hãy coi module.exports như thể nó là một default export." Đây chỉ là thiết lập kiểm tra kiểu — output JavaScript sau khi biên dịch vẫn hoàn toàn giữ nguyên.
Một điều đáng lưu ý: khi bật esModuleInterop, allowSyntheticDefaultImports cũng sẽ tự động được bật theo. Chi tiết hơn ở Cách sửa 2.
Cách Sửa 1 — Bật allowSyntheticDefaultImports trong tsconfig.json (Khuyến nghị)
Mở tsconfig.json và thêm flag vào compilerOptions:
{
"compilerOptions": {
"allowSyntheticDefaultImports": true,
// ... các tùy chọn còn lại
}
}
Lưu lại và chạy lại tsc. Lỗi sẽ biến mất. Thay đổi một dòng này giải quyết được vấn đề trong phần lớn các dự án.
Cách Sửa 2 — Dùng esModuleInterop Thay Thế (Tốt Hơn cho Dự Án Node.js/CommonJS)
Hãy xem esModuleInterop là phiên bản nâng cấp. Nó làm được mọi thứ mà allowSyntheticDefaultImports làm — đồng thời còn inject các runtime helper để kết nối CommonJS interop đúng cách trong JavaScript đã biên dịch. Với các dự án Node.js hoặc bất kỳ dự án nào bundle các package CommonJS, hãy ưu tiên dùng option này:
{
"compilerOptions": {
"esModuleInterop": true,
// allowSyntheticDefaultImports tự động bằng true khi esModuleInterop bằng true
}
}
Với cấu hình này, default import sẽ hoạt động bình thường:
import express from 'express'; // ✅
import React from 'react'; // ✅
import _ from 'lodash'; // ✅
import moment from 'moment'; // ✅
Cách Sửa 3 — Dùng Namespace Import (Không Cần Thay Đổi tsconfig)
Không thể động vào cấu hình TypeScript? Hãy chuyển sang namespace import:
// Thay vì viết:
import express from 'express'; // ❌
// Hãy dùng:
import * as express from 'express'; // ✅
Cách này hoạt động vì module vẫn export ra thứ gì đó — chỉ là không phải dưới dạng named default. Namespace import lấy toàn bộ object module.exports. Tuy nhiên cần lưu ý: cú pháp này trông hơi lạ, và một số định nghĩa kiểu của thư viện giả định bạn dùng default import syntax. Sự không khớp đó có thể gây ra các lỗi kiểu phụ ở các chỗ khác trong code.
Kiểm Tra Sau Khi Sửa
Chạy trình biên dịch để xác nhận mọi thứ đã sạch lỗi:
# Dùng tsc trực tiếp
npx tsc --noEmit
# Hoặc nếu bạn có build script
npm run build
Trong dự án Vite hoặc CRA, chỉ cần lưu file là xong. Dev server tự biên dịch lại, và banner lỗi đỏ trên trình duyệt sẽ biến mất trong vài giây.
Muốn kiểm tra xem flag nào đang thực sự có hiệu lực? Chạy lệnh này:
npx tsc --showConfig | grep -E 'allowSyntheticDefaultImports|esModuleInterop'
Kết quả mong đợi:
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
Tình Huống Thường Gặp: Create React App / Vite
Cả CRA lẫn Vite đều mặc định bao gồm esModuleInterop: true trong tsconfig.json được tạo ra. Vì vậy nếu bạn gặp lỗi này trong một dự án CRA hoặc Vite mới tạo, nghĩa là có gì đó đã thay đổi sau khi khởi tạo. Có thể flag đã bị xóa thủ công, hoặc bạn đang kế thừa một config gốc ghi đè lên nó. Hãy kiểm tra cả hai file:
{
"extends": "./tsconfig.base.json", // ← kiểm tra file này nữa
"compilerOptions": { ... }
}
Tình Huống Thường Gặp: Chuyển Đổi Từ JavaScript
Đổi tên các file .js thành .ts và lần đầu khởi động TypeScript? Hãy chuẩn bị gặp lỗi này ngay lần biên dịch đầu tiên. Các package như express, lodash, và moment đều sẽ kích hoạt nó. Thêm esModuleInterop: true vào tsconfig.json như một phần của quá trình thiết lập migration — cách này giải quyết toàn bộ trong một lần.
Mẹo Nhanh
- Ưu tiên dùng
esModuleInterop: truehơn là chỉ dùngallowSyntheticDefaultImports: true— an toàn hơn cho CommonJS interop và đây là khuyến nghị của team TypeScript. - Đang viết thư viện cho người khác dùng? Hãy cẩn thận với
esModuleInterop. Nó thay đổi cách JavaScript đã biên dịch xử lý import, điều này có thể gây bất ngờ cho người dùng thư viện nếu họ không dùng cùng thiết lập. - Một số package (bao gồm
@types/node) đã được cập nhật để export ES default đúng chuẩn. Nếu một package được bảo trì tốt vẫn kích hoạt lỗi này, thủ phạm hầu như chắc chắn là do tsconfig của bạn, không phải do type definitions. - Đừng trộn lẫn namespace import (
import * as X from '...') và default import (import X from '...') cho cùng một module — hãy chọn một kiểu và dùng nhất quán trong suốt file.

