Điều hướng chuyển đổi từ CommonJS sang ESM trong Node.js: Những điểm khó khăn, tiến trình và phương pháp hay nhất

By hungpd, at: 15:29 Ngày 21 tháng 8 năm 2025

Thời gian đọc ước tính: __READING_TIME__ phút

Navigating the CommonJS-to-ESM Transition in Node.js: Pain Points, Progress, and Best Practices
Navigating the CommonJS-to-ESM Transition in Node.js: Pain Points, Progress, and Best Practices

1. Giới thiệu: Cuộc chiến Module

 

Trong hơn một thập kỷ, các nhà phát triển Node.js đã sống trong một thế giới CommonJS. Mỗi require('fs')module.exports = foo đã định nghĩa DNA của backend JavaScript. Nhưng vào năm 2015, ủy ban ECMAScript đã giới thiệu ES Modules (ESM) với import và export, thiết lập một tiêu chuẩn phổ quát cho cả trình duyệt và môi trường runtime.

 

Chuyển tiếp đến Node.js 24: ESM không còn là một thử nghiệm mà đã là hướng đi mặc định. Các framework như Next.js, Remix, và Astro đã "ESM-first", trong khi những người bảo trì gói đang loại bỏ hỗ trợ CommonJS để giảm chi phí bảo trì.

 

Tuy nhiên... nhiều nhà phát triển vẫn phàn nàn. Tại sao? Bởi vì việc di chuyển một hệ sinh thái lớn như npm (hơn 2.5 triệu gói) giống như điều khiển một con tàu chở hàng, chậm chạp, đau đớn và đầy những trường hợp ngoại lệ.

 

2. Tại sao Quá trình Chuyển đổi này lại Đau đớn

 

Sự khác biệt về cú pháp

 

  • CommonJS:

const fs = require('fs');
module.exports = function hello() { return 'world'; }

 

  • ESM:

import fs from 'node:fs';
export default function hello() { return 'world'; }

 

Điều này có thể trông chỉ là mỹ phẩm, nhưng các hệ quả (độ phân giải tĩnh so với động, hoisting, async import) lại rất sâu sắc.

 

Phần mở rộng tệp & "type": "module" Gây nhầm lẫn

 

  • .cjs → Luôn là CommonJS
     

  • .mjs → Luôn là ESM
     

  • .js → Phụ thuộc vào trường "type" của package.json
     

    Điều này đã làm khó khăn cho vô số đội, một tệp đột nhiên bị lỗi vì chế độ sai đã được suy ra.

 

Hệ sinh thái Chậm chạp

 

  • Một số thư viện quan trọng (ví dụ: các middleware express cũ) cho đến gần đây mới chỉ hỗ trợ CJS.
     

  • Nhiều nhà phát triển vẫn gắn bó với CJS để tránh các vấn đề về công cụ với Jest, Webpack hoặc Mocha.

 

3. Cải tiến Gần đây của Node.js (v22 → v24)

 

  • Nâng cấp Tương tác:

    Node 22 đã giới thiệu khả năng require() toàn bộ đồ thị module ESM từ CommonJS, loại bỏ một trong những rào cản lớn nhất.
     

  • Thông báo Lỗi Tốt hơn:

    Thay vì thông báo khó hiểu "ERR_REQUIRE_ESM", Node giờ đây cho bạn biết lý do một module không thể được nhập và đề xuất các bản sửa lỗi.
     

  • Thuộc tính Import:

    Node 24 (V8 13.6) hỗ trợ native các assertion import:
     

import data from './data.json' assert { type: 'json' };

 

  • Không cần trình tải hoặc cờ thử nghiệm.
     

  • Áp lực từ Framework:

    Next.js và các framework khác hiện chỉ cung cấp bản dựng ESM. Nếu bạn không tham gia, bạn sẽ bị bỏ lại phía sau.

 

4. Di chuyển trong Thực tế: Các Kịch bản

 

4.1 Di chuyển Dự án Nhỏ

 

  1. Thêm "type": "module" vào package.json.
     

  2. Cập nhật các dòng import:

     

    • require('foo') → import foo from 'foo'
       

    • module.exports = → export default

 

Trước đây (CJS):

 

const moment = require('moment');
module.exports = () => moment().format();

 

Sau đó (ESM):

 

import moment from 'moment';
export default () => moment().format();

 

4.2 Hỗ trợ cả hai thế giới (Tác giả thư viện)

 

Sử dụng xuất có điều kiện:

 

{
  "exports": {
    "import": "./esm/index.js",
    "require": "./cjs/index.cjs"
  }
}

 

Theo cách này, người dùng ESM nhận được mã hiện đại, trong khi các ứng dụng cũ vẫn chạy trên CJS.

 

4.3 Các phụ thuộc cũ

 

Đối với các gói vẫn chưa hỗ trợ ESM, Node cung cấp `createRequire`:

 

import { createRequire } from 'node:module';
const require = createRequire(import.meta.url);
const legacy = require('old-package');

 

5. Ưu điểm so với Nhược điểm

 

Khía cạnh Ưu điểm ESM Thách thức / Đánh đổi
Chuẩn hóa Hệ thống module giống nhau trên trình duyệt & Node Hỗ trợ kép làm tăng độ phức tạp cho thư viện
Hiệu suất Phân tích tĩnh cho phép tree-shaking & xây dựng nhanh hơn Phạt khởi động nguội trong một số trường hợp (async imports)
Trải nghiệm nhà phát triển (DX) Cú pháp gọn gàng hơn; import() không đồng bộ Nhầm lẫn với .cjs, .mjs, và "type"
Hệ sinh thái Các framework hiện đại là ESM-first Các gói cũ có thể không bao giờ di chuyển

 

6. Các thực tiễn Tốt nhất

 

  1. Dự án Mới → Luôn là ESM ("type": "module").
     

  2. Thư viện → Xuất kép cho đến khi CJS mờ dần.
     

  3. Ứng dụng → Di chuyển Dần dần: bắt đầu với các module lá, không phải các điểm vào cốt lõi.
     

  4. Kiểm tra Công cụ: Đảm bảo bộ đóng gói / khung kiểm thử của bạn có hỗ trợ ESM đầy đủ.
     

  5. Đào tạo Đội ngũ: Tài liệu hóa các mẫu như `import.meta.url` và `createRequire`.

 

 

7. Kết luận

 

Sự chuyển đổi từ CommonJS sang ESM mang tính văn hóa nhiều hơn là kỹ thuật. Nhiều nhà phát triển cảm thấy "mệt mỏi vì di chuyển", nhưng vào năm 2025, chống lại ESM giống như từ chối sử dụng Git vào năm 2010, bạn sẽ bị bỏ lại phía sau.

 

Nhóm Node.js đã làm phần việc của mình: tương tác tốt hơn, ít cờ hơn, lỗi rõ ràng hơn. Bây giờ là lúc các nhà phát triển và người bảo trì áp dụng tiêu chuẩn.

 

Bài học rút ra: Bắt đầu nhỏ, di chuyển dần dần, và bạn sẽ mở khóa một codebase gọn gàng hơn, nhanh hơn và bền vững hơn với tương lai.

Tag list:

Theo dõi

Theo dõi bản tin của chúng tôi và không bao giờ bỏ lỡ những tin tức mới nhất.