require như một thói quen. Rồi một ngày bạn mở một project TypeScript, hoặc đọc một tutorial hiện đại, và bắt gặp import express from 'express' — trông gọn hơn, “xịn” hơn, nhưng lại không rõ nó khác gì require không, hay chỉ là cú pháp khác cho cùng một thứ?
Bài viết này sẽ giúp bạn hiểu rõ hai hệ thống module này, cách chuyển đổi tư duy từ CJS sang ESM, và đặc biệt là exports vs export default — thứ hay gây nhầm lẫn nhất khi mới chuyển sang TypeScript.
Hai hệ thống module trong JavaScript
JavaScript có hai hệ thống module hoàn toàn khác nhau:- CommonJS (CJS) — ra đời cùng Node.js, dùng
require/module.exports - ES Modules (ESM) — chuẩn hiện đại của JavaScript, dùng
import/export
CommonJS — Người bạn cũ của Express
Nếu bạn đang viết Express, bạn đang dùng CJS:require() đọc file, thực thi ngay, trả về kết quả. Đơn giản và dễ đoán.
ES Modules — Chuẩn hiện đại
ESM là cú phápimport/export bạn thấy trong TypeScript và các framework hiện đại:
exports vs export: Coi chừng dư chữ “s”
Đây là điểm hay bị nhầm nhất: coi chừng dư chữ “s”. Hãy đối chiếu trực tiếp:
Xuất nhiều thứ (Named Export)
Lưu ý: Vớidefault export, bạn đặt tên gì khi import cũng được. Vớinamed export, tên phải khớp (hoặc dùngasđể đổi tên).
Bảng đối chiếu đầy đủ
import trong TypeScript hoạt động thế nào?
Khi bạn viết TypeScript với cấu hình mặc định ("module": "commonjs" trong tsconfig.json):
tsconfig.json:
"module": "commonjs" là đủ và ít rắc rối hơn.
Một số lưu ý thực tế
✅ Nên làm:- Trong TypeScript, dùng
import/exportnhất quán — đừng trộn vớirequire - Dùng
export defaultcho thứ chính của file (router, class, function chính) - Dùng
named exportkhi file xuất nhiều thứ ngang hàng nhau (models, utils…)
- Mỗi file nên có một phong cách export nhất quán — hoặc
default, hoặcnamed, không nên trộn - File
models/index.tsthường dùngnamed exportđể re-export nhiều model - File
routes/user.tsthường dùngexport defaultvì chỉ xuất một router
Tóm tắt
- CommonJS (
require/module.exports) là hệ thống module mặc định của Node.js, dùng phổ biến trong Express - ESM (
import/export) là chuẩn hiện đại, TypeScript sử dụng cú pháp này - TypeScript mặc định biên dịch
**import**xuống**require**— nên runtime vẫn là CJS module.exports = { a, b }tương đươngexport { a, b }— đều là named exportmodule.exports = xtương đươngexport default x— đều là default export
import/export và TypeScript sẽ lo phần còn lại.
Bước tiếp theo: Khi đã quen với TypeScript, bạn có thể tìm hiểu thêm về path alias (@/models/User thay vì ../../models/User), barrel file (pattern dùng index.ts để re-export), và cách tổ chức module trong một project Express + TypeScript lớn hơn.