CommonJS Modules: Mở Khóa Sức Mạnh Tổ Chức Code Node.js (Genz Edition)
Nodejs

CommonJS Modules: Mở Khóa Sức Mạnh Tổ Chức Code Node.js (Genz Edition)

Author

Admin System

@root

Ngày xuất bản

18 Mar, 2026

Lượt xem

49 Lượt

"CommonJS modules"

CommonJS Modules: Mở Khóa Sức Mạnh Tổ Chức Code Node.js (Genz Edition)

Chào các bạn Gen Z của anh Creyt! Hôm nay, chúng ta sẽ cùng nhau 'unbox' một khái niệm tưởng chừng khô khan nhưng lại là xương sống của mọi dự án Node.js đời đầu: CommonJS Modules. Đừng lo, anh sẽ 'hack' nó thành thứ gì đó dễ hiểu, dễ nhớ nhất.

1. CommonJS Modules là gì mà 'hot' thế?

Để dễ hình dung, các bạn cứ coi dự án code của chúng ta như một khu chung cư cao cấp. Mỗi căn hộ (file .js) là nơi ở của một 'team' code, chuyên làm một nhiệm vụ cụ thể nào đó (ví dụ: căn hộ A chuyên tính toán, căn hộ B chuyên xử lý database).

CommonJS Modules chính là cái 'quy tắc quản lý chung' của khu chung cư này, giúp các căn hộ có thể trao đổi đồ đ đạc (dữ liệu, hàm) cho nhau một cách có trật tự, không ai lấn sang 'lãnh thổ' của ai.

Nói một cách hàn lâm hơn, CommonJS là một đặc tả (specification) định nghĩa cách các module (các file JavaScript) có thể được định nghĩa, xuất (export)nhập (import) vào các file khác trong môi trường Node.js. Nó là hệ thống module mặc định của Node.js trong một thời gian rất dài, trước khi ES Modules (ESM) 'nhảy vào cuộc chơi'.

Nó sinh ra để làm gì?

  • Tránh 'ô nhiễm' không gian toàn cục (Global Scope Pollution): Tưởng tượng mỗi căn hộ (file code) đều vứt đồ đạc ra hành lang chung (global scope). Chẳng mấy chốc sẽ thành bãi rác, không biết đồ của ai với ai. CommonJS giúp mỗi căn hộ giữ đồ đạc của mình, chỉ chia sẻ những gì cần thiết.
  • Tái sử dụng code (Code Reusability): Viết một lần, dùng nhiều nơi. Như việc bạn có một cái máy pha cà phê xịn (một hàm tiện ích) ở căn hộ mình, thay vì mỗi lần muốn uống lại phải mua máy mới, bạn chỉ cần 'export' nó ra, căn hộ khác 'import' vào dùng là xong.
  • Dễ quản lý (Maintainability): Code được chia nhỏ thành các module độc lập, dễ dàng tìm kiếm, sửa lỗi và nâng cấp.
  • Rõ ràng về phụ thuộc (Dependency Management): Ai cần gì, lấy từ đâu là rõ ràng. Không còn cảnh 'đồ của tôi tự nhiên xuất hiện' mà không biết từ đâu ra.

2. Code Ví Dụ Minh Họa: 'Mở cửa' và 'Đặt đồ' trong khu chung cư code

Trong CommonJS, chúng ta có hai 'thao tác' chính:

  • require(): Đây là 'chìa khóa' để bạn 'mở cửa' và 'lấy đồ' từ một căn hộ (module) khác. Khi bạn require một module, Node.js sẽ tải module đó và trả về những gì nó đã 'export'.
  • module.exports (hoặc exports): Đây là 'hộp thư' hoặc 'bảng hiệu' của căn hộ bạn, nơi bạn 'đặt đồ' (hàm, biến, đối tượng) để các căn hộ khác có thể 'lấy' khi dùng require.

Ví dụ 'Căn hộ tiện ích' (utils.js)

// utils.js - Căn hộ chuyên làm các việc vặt tiện ích

function add(a, b) {
  console.log('Đang thực hiện phép cộng...');
  return a + b;
}

function subtract(a, b) {
  console.log('Đang thực hiện phép trừ...');
  return a - b;
}

const PI = 3.14159;

// 'Đặt đồ' vào hộp thư module.exports để căn hộ khác lấy
module.exports = {
  addFunction: add,       // Đặt hàm 'add' dưới tên 'addFunction'
  subtractFunction: subtract, // Đặt hàm 'subtract' dưới tên 'subtractFunction'
  PI_CONSTANT: PI         // Đặt biến 'PI' dưới tên 'PI_CONSTANT'
};

// Hoặc bạn có thể 'đặt từng món đồ' riêng lẻ:
// exports.addFunction = add;
// exports.subtractFunction = subtract;
// exports.PI_CONSTANT = PI;
// Lưu ý: Không gán trực tiếp 'exports = ...' mà phải gán 'module.exports = ...' nếu muốn thay đổi toàn bộ đối tượng exports.

Ví dụ 'Căn hộ chính' (app.js)

// app.js - Căn hộ chính, cần dùng tiện ích từ utils.js

// Dùng 'chìa khóa' require để 'mở cửa' và 'lấy đồ' từ utils.js
// Lưu ý: Đường dẫn './utils' là đường dẫn tương đối đến file utils.js
const myUtils = require('./utils'); 

// Giờ thì dùng 'đồ' đã lấy được thôi!
console.log('Kết quả 5 + 3 =', myUtils.addFunction(5, 3)); // Output: Đang thực hiện phép cộng...
                                                        //         Kết quả 5 + 3 = 8

console.log('Kết quả 10 - 4 =', myUtils.subtractFunction(10, 4)); // Output: Đang thực hiện phép trừ...
                                                                //         Kết quả 10 - 4 = 6

console.log('Hằng số PI là:', myUtils.PI_CONSTANT); // Output: Hằng số PI là: 3.14159

// Bạn cũng có thể dùng cú pháp 'destructuring' để lấy trực tiếp các món đồ:
const { addFunction, PI_CONSTANT } = require('./utils');
console.log('PI từ destructuring:', PI_CONSTANT); // Output: PI từ destructuring: 3.14159
console.log('2 + 7 =', addFunction(2, 7)); // Output: Đang thực hiện phép cộng...
                                        //         2 + 7 = 9

Để chạy ví dụ này, bạn chỉ cần tạo hai file utils.jsapp.js trong cùng một thư mục, sau đó mở terminal tại thư mục đó và gõ:

node app.js
Illustration

3. Mẹo (Best Practices) từ 'Giáo sư' Creyt

  • 'Quy hoạch' rõ ràng: Luôn nghĩ xem module của bạn sẽ 'export' ra những gì. Đừng 'vứt' tất cả mọi thứ ra module.exports. Chỉ chia sẻ những gì module đó chịu trách nhiệm và cần thiết cho module khác.
  • Đường dẫn 'chuẩn chỉ': Khi require các module tự viết trong dự án, luôn dùng đường dẫn tương đối (ví dụ: ./myModule, ../anotherModule). Khi require các thư viện cài từ npm (ví dụ: express, lodash), chỉ cần dùng tên gói.
  • module.exports là 'ông chủ': Khi muốn xuất một đối tượng, hàm, hay giá trị duy nhất từ module, hãy dùng module.exports = .... Nếu muốn xuất nhiều thứ, hãy gán các thuộc tính vào module.exports (hoặc exports). Nhớ rằng exports chỉ là một tham chiếu đến module.exports ban đầu; nếu bạn gán exports = { ... }, tham chiếu sẽ bị đứt và module sẽ xuất ra một đối tượng rỗng. Tốt nhất, cứ dùng module.exports cho rõ ràng.
  • 'Cache' là bạn: Node.js sẽ cache module sau lần require đầu tiên. Điều này có nghĩa là nếu bạn require cùng một module nhiều lần, nó sẽ không chạy lại code trong module đó mà chỉ trả về đối tượng đã được cache. Điều này giúp tối ưu hiệu suất nhưng cũng cần lưu ý nếu module của bạn có 'side effects' (tác dụng phụ) khi được tải.
  • Đồng bộ (Synchronous) là 'đặc sản': require() là một hoạt động đồng bộ. Điều này có nghĩa là code của bạn sẽ dừng lại cho đến khi module được tải xong. Với các module code thuần túy thì không sao, nhưng nếu bạn require một module mà bên trong nó làm các tác vụ I/O nặng (ví dụ: đọc file lớn), nó có thể làm chậm ứng dụng.

4. Góc học thuật Harvard: CommonJS dưới kính hiển vi

Tại sao exports, require, module, __filename, __dirname lại 'tự nhiên xuất hiện' trong mỗi file Node.js mà chúng ta không cần khai báo?

Đó là vì Node.js không chạy code của bạn trực tiếp. Thay vào đó, nó bao bọc (wraps) mỗi module trong một hàm đặc biệt, trông giống như thế này:

(function (exports, require, module, __filename, __dirname) {
  // Code của bạn ở đây
  // Ví dụ: console.log('Hello from module');
  //        module.exports = someValue;
});

Khi Node.js tải một module, nó thực thi hàm bao bọc này và truyền vào các đối tượng exports, require, module, __filename, __dirname làm tham số. Điều này tạo ra một không gian cục bộ (local scope) cho mỗi module, giữ cho các biến và hàm không bị xung đột với các module khác hoặc không gian toàn cục.

  • module: Là một đối tượng chứa thông tin về module hiện tại, quan trọng nhất là thuộc tính exports của nó.
  • module.exports: Là đối tượng mà module sẽ trả về khi được require.
  • exports: Ban đầu, exports là một tham chiếu đến module.exports. Bạn có thể thêm thuộc tính vào exports (ví dụ: exports.myFunc = ...). Tuy nhiên, nếu bạn gán exports = { ... }, bạn đã thay đổi tham chiếu của exports mà không thay đổi module.exports, dẫn đến việc module vẫn trả về module.exports ban đầu (thường là một đối tượng rỗng).
  • require: Hàm dùng để nhập các module khác.
  • __filename: Đường dẫn tuyệt đối đến file module hiện tại.
  • __dirname: Đường dẫn tuyệt đối đến thư mục chứa file module hiện tại.

5. Ứng dụng thực tế: Ai đang dùng CommonJS?

CommonJS là 'công thần' của Node.js, nên bạn sẽ thấy nó ở khắp mọi nơi, đặc biệt là trong các dự án cũ hơn:

  • Express.js: Framework web 'quốc dân' của Node.js, trong các phiên bản cũ và cấu hình mặc định, vẫn sử dụng CommonJS.
  • Lodash, Mongoose, Moment.js: Hầu hết các thư viện utility, ORM/ODM phổ biến được phát triển cho Node.js đều dùng CommonJS.
  • Các công cụ Build: Webpack, Gulp, Grunt (trước đây) thường sử dụng CommonJS cho các file cấu hình của chúng.
  • Backend của các ứng dụng lớn: Nhiều dịch vụ backend của các công ty như PayPal, Netflix, Uber (một số microservices) sử dụng Node.js, và nhiều phần trong đó vẫn đang chạy trên CommonJS modules.
  • Các CLI Tools (Command Line Interface Tools): Nhiều công cụ dòng lệnh được viết bằng Node.js cũng dùng CommonJS để tổ chức code.

6. Thử nghiệm và Nên dùng cho trường hợp nào?

Khi nào nên 'chiến' với CommonJS?

  • Dự án Node.js cũ: Nếu bạn đang 'đào mộ' hoặc bảo trì một dự án Node.js được xây dựng từ lâu, khả năng cao nó đang dùng CommonJS. Nắm vững nó là chìa khóa để hiểu và sửa đổi code.
  • Thư viện chỉ hỗ trợ CommonJS: Một số thư viện cũ hơn có thể chưa chuyển sang ES Modules. Trong trường hợp này, bạn buộc phải dùng CommonJS để require chúng.
  • Khi package.json không có "type": "module": Theo mặc định, Node.js coi các file .js là CommonJS modules. Nếu bạn không khai báo "type": "module" trong package.json, thì đó là CommonJS.
  • Các script Node.js đơn giản: Đối với các script backend nhỏ, nhanh gọn, CommonJS vẫn là lựa chọn tiện lợi và không cần cấu hình phức tạp.

Khi nào nên 'nhảy tàu' sang ES Modules (cho Gen Z muốn 'up-to-date')?

  • Dự án Node.js mới: Nếu bạn đang bắt đầu một dự án Node.js hoàn toàn mới, anh Creyt khuyến khích bạn nên cân nhắc sử dụng ES Modules (với cú pháp import/export). Nó là tiêu chuẩn của JavaScript hiện đại, tương thích tốt hơn với trình duyệt và có nhiều tính năng 'xịn sò' hơn như top-level await.
  • Cần 'Tree Shaking': Đối với các ứng dụng frontend được bundle (ví dụ với Webpack), ES Modules cho phép 'tree shaking' hiệu quả hơn, loại bỏ code không dùng đến để giảm kích thước bundle.
  • Thống nhất với Frontend: Nếu bạn làm cả frontend và backend, việc dùng ES Modules ở cả hai nơi giúp codebase đồng nhất hơn.

Thử nghiệm ngay!

Để thực sự 'thấm' CommonJS, hãy tự mình thử nghiệm:

  1. Tạo một thư mục mới, ví dụ my-commonjs-project.
  2. Trong thư mục đó, tạo hai file: calculator.jsmain.js.
  3. Trong calculator.js, viết vài hàm toán học (cộng, trừ, nhân, chia) và module.exports chúng ra.
  4. Trong main.js, require calculator.js và gọi các hàm đó.
  5. Chạy node main.js và xem kết quả.
  6. Thử thay đổi cách export (dùng exports.func = ... hoặc module.exports = { ... }) và cách require (destructuring) để xem sự khác biệt.

Đây là cách tốt nhất để 'đúc kết' kiến thức và biến nó thành kinh nghiệm của riêng bạn. Hãy nhớ, code là để thực hành, không phải chỉ để đọc!

Chúc các bạn Gen Z 'code' vui vẻ và trở thành những dev 'đỉnh của chóp'!

Thuộc Series: Nodejs

Bài giảng này được tự động xuất bản ngẫu nhiên từ thư viện kiến thức. Đừng quên đón xem các Từ khoá Hướng Dẫn tiếp theo nhé!

#tech #cyberpunk #laravel
Chỉnh sửa bài viết

Bình luận (0)

Vui lòng Đăng Nhập để Bình luận

Hỗ trợ Markdown cơ bản
Nguyễn Văn A
1 ngày trước

Tính năng này đỉnh quá ad ơi, chờ mãi mới thấy một blog Tiếng Việt có UI/UX xịn như vầy!