Module là các thành phần cơ bản để xây dựng nên một ứng dụng Node. Một module chứa các hàm API, tức là chúng ta chỉ biết chính xác hàm đó có tên gì, có tham số gì, dữ liệu trả về là gì chứ không biết chi tiết chúng hoạt động như thế nào.
Trong các bài trước chúng ta đã làm việc với module, bản thân mỗi file JavaScript đã là một module rồi. Trong phần này chúng ta sẽ tìm hiểu sâu hơn.
Định nghĩa một module
Trong bài trước chúng ta có viết một file có tên ls.js,
trong file đó có dòng code như sau:
var fs = require('fs');
Trong dòng code trên chúng ta dùng hàm require(),
hàm này có chức năng tìm module fs
và chép vào trình thông dịch, và chúng ta có thể sử dụng các hàm có trong module đó. Đối tượng fs
(biến var fs
) chứa code được định nghĩa trong module fs
.
Tiếp theo chúng ta xem xét ví dụ sau:
var count = 0; exports.next = function() { return count++; }
Chúng ta tạo một file có tên simple.js
với đoạn code trên, sau đó mở terminal lên và gõ lệnh node,
chỉ gõ node
để mở trình thông dịch Node lên thôi chứ không chạy file simple.js.
Lưu ý nếu thư mục hiện tại trên terminal không phải là thư mục chứa file simple.js
thì bạn phải chuyển đường dẫn đến thư mục đó (bằng lệnh cd
trên cả Windows và Linux, nếu không biết bạn có thể tìm trên Google cách dùng). Sau đó chúng ta chạy một số lệnh như sau:
C:\Users\PhoCode>node > var s = require('./simple'); > s.next(); 0 > s.next(); 1 > s.next(); 2 > s.next(); 3 >
Trong file simple.js,
đối tượng exports
được trả về từ hàm require('./simple').
Cũng có nghĩa là cứ mỗi lần chúng ta gọi s.next()
thì hàm next()
được định nghĩa trong file simple.js
sẽ được gọi, ở đây hàm next()
trả về giá trị của biến count
và tăng biến đó lên 1 đơn vị, do đó các lần gọi tiếp theo giá trị sẽ tăng dần.
Quy tắc ở đây là bất cứ thứ gì từ hàm cho tới đối tượng nào mà được gán vào làm một trường của đối tượng exports
thì đều có thể gọi được ở ngoài, nếu không phải là một trường của đối tượng exports
thì sẽ không thể gọi tới được, cho dù có require()
hay không.
Lưu ý là các đối tượng global
được định nghĩa trong một file .js
cũng chỉ có thể nhìn thấy ở trong file đó thôi, chứ không thể gọi từ file khác được, nếu muốn gọi từ nơi khác thì chúng ta cũng phải gán vào đối tượng exports.
Các module trong Node
Các module có trong Node thường được viết theo cách viết của module CommonJS (chúng ta sẽ tìm hiểu về CommonJS sau) nhưng không giống hoàn toàn. Trong Node, mỗi module được lưu trong một file, cách lưu các file này cũng rất linh hoạt.
Tên một module chính là tên file .js
nhưng không có phần đuôi, tức là chúng ta viết require('./simple')
thì Node sẽ tự hiểu là tìm file simple.js.
Ngoài các module là code thì Node cũng hỗ trợ các thư viện đã được dịch ra thành file nhị phân, ở đây các file này có đuôi là .node
. Tuy nhiên chúng ta sẽ không đi sâu vào tìm hiểu các module kiểu này, bạn chỉ cần nhớ là các file có đuôi .node
là các file module bình thường như các file .js
thôi, có điều là chúng đã được dịch thành mã nhị phân.
Ngoài ra còn có các module cốt lõi đã được biên dịch thành file khả thi (có đuôi .exe trong Windows).
Đường dẫn để tìm module của Node có 3 loại là đường dẫn tương đối, đường dẫn tuyệt đối hoặc top-level.
Ví dụ:
- Đường dẫn tương đối :
./simple.js, ../simple.js
- Đường dẫn tuyệt đối :
C:/simple.js
- Đường dẫn top-level:
simple.js
(module nằm chung thư mục)
Module nằm trong một app
Thường thì các module sẽ nằm trong một ứng dụng nào đó hoặc không.
Một ứng dụng thường là một thư mục chứa các file và thư mục con trong đó, các thư mục con này có thể chứa file code, file tài nguyên, hoặc chứa các module khác. Các module biết chúng được đặt ở đâu và khi cần thì có thể gọi đến nhau một cách dễ dàng.
Ví dụ dưới đây là thư mục của một gói rất phổ biến trong Node là gói Express, gói này chứa các file và thư mục con bên trong nó theo cấu trúc cây.
express/ bin/ History.md index.js lib/ express.js http.js https.js request.js response.js /router collection.js index.js methods.js route.js utils.js view/ partial.js view.js view.js LICENSE Makefile node_modules\ qs\ package.json Readme.md
Trong gói Express thì module thường được sử dụng nhiều nhất là utils.js.
Tùy vào vị trí được đặt ở đâu mà các module khác có câu lệnh gọi khác nhau, ví dụ:
var utils = requrire('./lib/utils'); var utils = requrire('./utils'); var utils = requrire('../utils');
Cách tìm module của Node
Ngoài thư mục của ứng dụng thì các module thường còn được đặt trong thư mục node_modules,
khi gọi thì chúng ta chỉ cần dùng tên module là đủ (tức là đường dẫn kiểu top-level), ví dụ:
var express = require('express');
Node sẽ tự động tìm module trong các thư mục node_modules,
thư mục này có rất nhiều, Node sẽ tìm thư mục node_modules
trong thư mục của file gọi đến module đó, nếu không thấy thì tiếp tục tiềm trong thư mục cha của thư mục chứa file gọi đến module đó và nếu không tìm thấy nữa thì Node sẽ tiếp tục đi dần dần lên các thư mục cha tiếp theo cho đến chạm đến thư mục gốc thì thôi.
Trong ví dụ trước, gói Express có một thư mục tên là node_modules,
trong thư mục này chứa một thư mục khác có tên là qs,
tức là module qs
và module này có thể được gọi tới bởi bất kì file nào trong thư mục express.
var qs = require('qs');
Như vậy tóm lại cách dễ nhất để gọi các module là tạo thư mục node_modules và đặt chúng ta trong thư mục này, thư mục node_modules
nên được đặt ở vị trí cao nhất để các module khác có thể thấy được. Ví dụ:
projects/ drawapp/ index.js lib/ draw.js node_modules/ svg.js node_modules/ express/ bin/ History.md index.js lib/ LICENSE Makefile node_modules/ qs/ package.json Readme.md package.json node_modules/
Trong ví dụ trên, những module nằm trong thư mục drawapp
có thể thấy được các module nằm trong thư mục projects/node_modules
, và ngược lại các module trong thư mục node_modules
này không thể thấy được module qs
vì qs
nằm ở trong cùng trong khi Node chỉ tìm các module bằng cách đi ngược về các thư mục cha.
Tương tự, những module nằm trong thư mục lib/node_modules
có thể thấy được từ draw.js
và svg.js
nhưng không thấy được từ index.js.
Lý do tại sao lại có thể có nhiều thư mục node_modules
là vì làm như thế thì Node cho phép chúng ta có thể dùng một module với nhiều phiên bản khác nhau thay vì chỉ dùng một thư mục duy nhất rồi chép tất cả vào gây lộn xộn.
Tìm trong biến NODE_PATH
Ngoài các thư mục node_modules
thì Node còn cho phép chúng ta đặt module trong các thư mục được định nghĩa trong biến môi trường NODE_PATH,
biến này lưu danh sách các đường dẫn thư mục mà Node có thể tìm trong đó, mỗi thư mục cách nhau bằng dấu chấm phẩy (trong Windows) hoặc dấu 2 chấm (trong Linux).