Đang tải...

Vén Màn Bí Mật var require: NodeRequire;: Hàm require Của Node.js Khủng Khiếp Hơn Bạn Nghĩ!

28/06/2026
9 phút đọc
Vén Màn Bí Mật var require: NodeRequire;: Hàm require Của Node.js Khủng Khiếp Hơn Bạn Nghĩ!
Chào anh em Viblo! 👋 Nếu anh em đã từng làm việc với Node.js ở kỷ nguyên CommonJS, hoặc đang cấu hình các file script, file webpack, vite trong các dự án hiện đại, ...

Chào anh em Viblo! 👋

Nếu anh em đã từng làm việc với Node.js ở kỷ nguyên CommonJS, hoặc đang cấu hình các file script, file webpack, vite trong các dự án hiện đại, chắc chắn anh em đã gõ câu lệnh này hàng vạn lần:

const fs = require('node:fs');

Chúng ta đều gọi miệng require() là một cái "hàm" dùng để nạp thư viện. Thế nhưng, nếu anh em dùng TypeScript và lỡ tay Ctrl + Click vào chữ require, anh em sẽ được dẫn tới một dòng khai báo toàn cục (global declaration) nằm sâu trong gói @types/node trông như thế này:

declare var require: NodeRequire;

Khựng lại một giây nào! Tại sao một cái "hàm" lại được khai báo bằng từ khóa var? Và cái kiểu dữ liệu NodeRequire kia chứa đựng những ma thuật gì bên trong? Hóa ra, require trong Node.js không đơn thuần là một cái hàm toàn cục global function như parseInt() hay setTimeout(), mà nó là một đối tượng cực kỳ quyền lực bọc trong một callable interface.

Hôm nay, hãy cùng mình mổ xẻ "thân phận thật sự" của var require: NodeRequire; để xem các kỹ sư Core Node.js đã thiết kế hệ thống module kinh điển này tinh tế như thế nào nhé!

1. Bóc Tách Bản Chất: require Là Một Hàm Hay Một Object?

Câu trả lời chính xác nhất là: Cả hai! Trong JavaScript, Hàm bản chất cũng là một Đối tượng (Function is an Object). Khai báo interface NodeRequire trong TypeScript đã tận dụng triệt để tính chất này để tạo ra một cấu trúc vừa có thể gọi trực tiếp giống như hàm, vừa có thể truy cập vào các thuộc tính/phương thức đính kèm trên nó.

Cấu trúc thu gọn của interface NodeRequire trong core sẽ trông như thế này:

interface NodeRequire {
 // 1. Cấu trúc Callable (Giúp require có thể gọi như một hàm)
 (id: string): any;

 // 2. Các thuộc tính và phương thức đính kèm
 resolve: RequireResolve;
 cache: Dict<Module>;
 extensions: RequireExtensions;
 main: Module | undefined;
}

2. Tại Sao Lại Khai Báo Bằng var Mà Không Phải const?

Có hai lý do cốt lõi khiến TypeScript buộc phải dùng declare var require ở tầng global:

  • Tương thích ngược với kiến trúc Global Scope cũ: Từ khóa var khi khai báo ở tầng global sẽ tự động đính kèm vào đối tượng toàn cục (global trong Node.js hoặc window trong Browser). Nếu dùng let hoặc const, biến đó sẽ bị giới hạn trong block scope của file định nghĩa và không phủ sóng toàn hệ thống được.
  • Cho phép "Hóa Thân" / Mocking khi Test: Vì khai báo bằng var, trong các môi trường Testing (như Jest, Mocha), các kỹ sư có thể tạm thời ghi đè hoặc bọc (spy/stub) lên hàm require gốc để kiểm tra xem một module có được nạp đúng cách hay không. Nếu khai báo bằng const, việc bọc hàm này là hoàn toàn bất khả thi.

3. Những "Vũ Khí Bí Mật" Nằm Trong NodeRequire (Kinh Nghiệm Thực Chiến)

Hàm require(id) thì ai cũng biết rồi, giờ chúng ta sẽ soi vào đống thuộc tính đính kèm – nơi phân biệt giữa một Dev biết xài và một Senior làm chủ công cụ.

Vũ khí 1: require.resolve(id) — Tìm đường nhưng không nạp

Hàm require('lodash') sẽ bốc nguyên toàn bộ code của thư viện Lodash nạp vào RAM. Nhưng nếu bạn chỉ muốn biết: "Cái file đó nằm chính xác ở thư mục nào trong ổ cứng?" mà không muốn tốn RAM nạp nó, hãy dùng .resolve().

try {
 const absolutePath = require.resolve('my-secret-config.json');
 console.log('File tồn tại tại:', absolutePath); 
 // 👉 Kết quả: /Users/project/src/my-secret-config.json
} catch (err) {
 console.log('File không tồn tại trong hệ thống!');
}
  • Ứng dụng thực chiến: Thường dùng để check xem một plugin, một file config có tồn tại trong project hay không trước khi kích hoạt một logic nào đó, hoặc dùng để truyền đường dẫn tuyệt đối vào các tool công cụ khác.

Vũ khí 2: require.cache — Cơn ác mộng và vị cứu tinh "Hot Reload"

Mặc định, Node.js cực kỳ thông minh: Khi bạn require('./config') lần đầu, nó sẽ đọc file từ ổ cứng và lưu kết quả vào một bộ nhớ đệm gọi là require.cache. Từ lần thứ hai trở đi, nó chỉ việc bốc từ RAM ra, tốc độ nhanh chớp nhoáng.

Vết sẹo thực chiến: Bạn làm một tool CMS, admin vừa thay đổi cấu hình hệ thống trong file config.json. Bạn muốn Node.js đọc lại file đó để cập nhật data mới mà không muốn phải restart lại toàn bộ Server. Nếu bạn cứ cắm đầu gọi lại require('./config.json'), dữ liệu nhận về vẫn là dữ liệu cũ vì Node.js đang lấy từ cache ra!

Bốc thuốc: Để ép Node.js đọc lại file mới từ ổ cứng, bạn phải chủ động xóa sạch cache của file đó trong require.cache:

const configPath = require.resolve('./config.json');

// Xóa bộ nhớ đệm của file cụ thể này
delete require.cache[configPath];

// Lần này Node.js buộc phải mò xuống ổ cứng đọc lại file mới tinh!
const freshConfig = require('./config.json');

Vũ khí 3: require.main — Xác định "Ai là cảnh sát trưởng?"

Thuộc tính này chứa thông tin của Module đóng vai trò là Điểm khởi chạy đầu tiên (Entry Point) của ứng dụng (Ví dụ file node app.js thì app.js chính là require.main).

  • Ứng dụng thực chiến: Giúp bạn viết một file vừa có thể làm thư viện cho file khác require, vừa có thể chạy độc lập như một tool CLI.
if (require.main === module) {
 console.log('File này đang được chạy trực tiếp bằng lệnh `node file.js`');
 // Chạy các script setup tự động tại đây...
} else {
 console.log('File này đang được một file khác require vào làm thư viện');
}

4. Góc Nhìn Thời Đại: Require vs Import

Mặc dù hệ sinh thái Node.js đang dịch chuyển mạnh mẽ sang ES Modules (ESM) với cú pháp import/export, nhưng requireNodeRequire vẫn giữ một vị trí tối thượng:

  • ESM chạy bất đồng bộ (Asynchronous), còn require chạy đồng bộ (Synchronous). Trong các script cấu hình hệ thống, việc nạp file tuần tự bằng require vẫn mang lại sự tiện lợi, mạch lạc và dễ kiểm soát lỗi hơn rất nhiều.
  • Hiểu sâu về require.cache hay require.resolve giúp anh em tự tin viết các công cụ build tool chuyên sâu, các plugin hệ thống hoặc tối ưu hóa hiệu năng nạp module cho các server có dung lượng RAM hạn chế.

Đúc Kết Lại

Dòng định nghĩa declare var require: NodeRequire; ngắn ngủi trong file core thực chất là một thiết kế mẫu mực thể hiện sự linh hoạt tuyệt đối của ngôn ngữ JavaScript. Bản chất của require là một hàm lai đối tượng (Callable Object), tích hợp sẵn các công cụ quản lý cache, định vị đường dẫn và định danh entry point cực kỳ mạnh mẽ bên dưới lớp cánh gà.

Hy vọng bài viết này giúp anh em "thông suốt" thêm một ngóc ngách thú vị của Node.js. Chúc anh em ứng dụng thành công và nâng trình viết script lên một tầm cao mới! Happy Coding!

📚 Nguồn: Viblo

Bình luận

0 bình luận

Email không hiển thị công khai.

Chưa có bình luận nào. Hãy là người đầu tiên bình luận.

Chia sẻ bài viết

Cần tư vấn?

Liên hệ với chúng tôi để được hỗ trợ

Liên hệ ngay

Bài viết liên quan

Giải Mã Từ Khóa super Trong JavaScript: "Chiếc Cầu Nối" Đến Lớp Cha Và Cú Vấp Bắt Buộc Trước Khi Chạm Vào this
28/06/2026

Giải Mã Từ Khóa super Trong JavaScript: "Chiếc Cầu Nối" Đến Lớp Cha Và Cú Vấp Bắt Buộc Trước Khi Chạm Vào this

Chào anh em Viblo! 👋 Khi thế giới JavaScript chuyển mình lên chuẩn ES6, cú pháp Class (Lớp) ra đời đã thay đổi hoàn toàn cách chúng ta viết code theo tư duy Lập trì...

Đọc thêm
Giải Mã var __dirname: string;: Tấm Bản Đồ Định Vị File Và Cú Vấp Ngã Xuyên Quốc Gia Giữa Windows và Linux
28/06/2026

Giải Mã var __dirname: string;: Tấm Bản Đồ Định Vị File Và Cú Vấp Ngã Xuyên Quốc Gia Giữa Windows và Linux

Chào anh em Viblo! 👋 Tiếp nối chuỗi bài viết mổ xẻ các tham số "quyền lực" được tiêm (inject) ngầm vào bên trong Module Wrapper Function của Node.js (sau khi chúng ...

Đọc thêm
Best 4 Sites to Buy Cash App Accounts (Fully Identity ...
28/06/2026

Best 4 Sites to Buy Cash App Accounts (Fully Identity ...

In today’s fast-paced digital economy, mobile payment applications have transformed how people send, receive, and manage money. One of the most widely used platforms in this space is Cash App, a sys...

Đọc thêm

Bắt đầu dự án của bạn

Hãy để Flash Dev đồng hành cùng bạn

Liên hệ ngay