Nodesjs - Require chưa bao giờ hết thắc mắc
TIL
500
@100daysTIL
43
White

ngminhtrung viết ngày 15/02/2018

Nodesjs - Require chưa bao giờ hết thắc mắc

Day22

Tham khảo:

Require(something) có gì?

Tạo 2 file, index.jshello.js trong cùng 1 folder, với nội dung như sau:

// hello.js
console.log("Hello Trung!");

// index.js
const hello = require('./hello');
console.log(module);

Chạy node index.js, rồi quan sát kết quả trong terminal:

Hello Trung
Module {
  id: '.',
  exports: {},
  parent: null,
  filename: '/backend/app-echo-server/index.js',
  loaded: false,
  children:
   [ Module {
       id: '/backend/app-echo-server/hello.js',
       exports: {},
       parent: [Circular],
       filename: '/backend/app-echo-server/hello.js',
       loaded: true,
       children: [],
       paths: [Array] } ],
  paths:
   [ '/backend/app-echo-server/node_modules',
     '/backend/node_modules',
     '/node_modules' ] }

Có thể thấy là:

  • "Hello Trung" là kết quả của const hello = require('./hello'):
    • Bạn require này bản thân cũng là 1 module, nhưng là global object nên ta không phải khai báo require('require'). Object này không có gì đặc biệt, nó chỉ đóng vai trò nhận vào tên hoặc đường dẫn của module ta muốn nhúng vào, sau đó trả về object module.exports.
    • Bạn require này nhúng code của hello.js vào index.js, nên dòng console.log("Hello Trung") trong hello.js mới được thực thi. Nhưng không phải nhúng tay bo mà còn được gói lại trong 1 lớp code nữa. Nói sau.
  • Bạn module cũng là 1 module (module tên "module"), là global object, không cần require('module').
    • Bạn module này giống như 1 ông quản gia ngầm, chứa hết các thông tin hầm bà làng về id,, exports, parent, filename, loaded, children, và path của các modules liên quan.
    • Cho dù có không gọi module qua console.log() thế kia, thì nó vẫn tồn tại ngầm phía sau.
    • Với module index.js:
    • id bằng '.' (folder gốc), trỏ đường dẫn đến module index.js
    • exports bằng {} (rỗng) -> index.js không export gì cả.
    • children thể hiện thông tin về module hello.js. Tức là Node.js coi module hello.jscon của module index.js. Bên trong module hello.js lại chứa 1 loạt các thuộc tính như id, exports, ... như trên.
    • path chứa rất hiều folder -> không hiểu tại sao lại lắm folder như thế.

Gọi module.exports hiện hình

Bây giờ thay đổi hello.js một chút, gọi bạn module.exports ra xem:

console.log("Hello Trung");

module.exports = function salute() {
    console.log("Bonjour, Trung!");
};

Trong đoạn code trên, ta đã gán hàm salute() vào module.exports. Lúc này đây, khi gọi lại node index.js, mọi thứ xuất ra trong terminal vẫn thế, trừ phần children:

 children:
   [ Module {
       id: '/backend/app-echo-server/hello.js',
       exports: [Function: salute],
       parent: [Circular],
       filename: '/backend/app-echo-server/hello.js',
       loaded: true,
       children: [],
       paths: [Array] } ],

À, vậy là Module.children.exports của hello.js không còn là rỗng {} nữa, mà đã thành [Function: salute].

Nghĩa là hàm salute() từ hello.js đã được nhúng thành công vào index.js, ta có thể sử dụng salute() trong index.js một cách tự nhiên. Công lớn thuộc về ai? Chắc là bạn object module. Cái này là thứ mà Lê Minh Tuấn trong bài NodeJS - require, exports, module.exports không nhắc đến.

Để làm tình hình phức tạp hơn, có thể:

  • require() thêm nhiều module khác
  • Không chỉ gán hàm vào module.exports, mà còn object chẳng hạn

để xem global object module nó hiện ra cái gì.

Điều gì xảy ra đằng sau require(something)?

Để "nhúng" được các module phụ vào module chính, Node.js đã thực hiện các bước sau:

# Bước Phiên âm Giải thích
1 Resolving /rəˈzälviŋ/ Đi tìm đường dẫn tuyệt đối của file
2 Loading /ˈləʊdɪŋ/ Xác định type của file
3 Wrapping /ˈræpɪŋ/ Bọc file lại, cho file 1 private scope. Biến object requiremodule thành dạng local.
4 Evaluating /ɪ'væljueɪt/ what the VM eventually does with the loaded code (không hiểu)
5 Caching /kæʃɪŋ/ cơ chế caching, để khi require một file nào một lần nữa, thì Node.js sẽ không chạy lại file đó từ đầu.

Tại sao lại cần bước resolving để đi tìm đường dẫn tuyệt đối của file chứa module?

Cần có bước này để đảm bảo Nodejs tìm và chỉ trỏ đến 1 và chỉ 1 file ứng với 1 module cho dù người dùng có đặt cái gì vào something trong require(something) đi chăng nữa. something có thể là:

  • core module/ package, ví dụ const filesystem = require('fs')
  • module/package của bên thứ ba, cài bằng npm, ví dụ: const express = require('express')
  • file JS đơn lẻ tự nghĩ ra, ví dụ: const server = require('./boot/server.js')
  • file JSON, ví dụ const databaseConfigs = require('./configs/database.json')
  • thậm chí cả 1 folder const routes = require('./routes') (gọi tắt thay vì gọi './routes/index.js'.

Vậy tìm ở đâu? ta chỉ nói chung chung là require(something) thì resolving sẽ tìm ở những folder nào? Nó tìm trong phần Module.path ở bên trên kia kìa. Theo thứ tự từ trên xuống dưới. Nếu để ý, bạn Node.js luôn chỉ tìm ở những folder "node_modules".

  paths:
   [ '/backend/app-echo-server/node_modules',
     '/backend/node_modules',
     '/node_modules' ] }

Điều đó có nghĩa là, nếu trong index.js, ta bỏ đường dẫn của hello.js đi, nghĩa là để const hello = require('hello.js'); thay vì const hello = require('./hello'); thì nó sẽ báo lỗi "Error: Cannot find module 'hello.js'" ngay tắp lự. Đơn giản là vì Module.path không chứa thư mục gốc.

Bây giờ, vẫn để const hello = require('hello.js'); (không cho đường dẫn), rồi tạo folder "node_modules" bên trong thư mục gốc, rồi nhét vào trong đó cũng 1 file hello.js với nội dung:

console.log("Tôi là hello.js giả mạo, đặt trong folder con 'node_modules', dưới folder chứa index.js một bậc");

gọi node index.js, ta sẽ thấy code chạy ngon lành, terminal hiện lên chữ:

Tôi là hello.js giả mạo, đặt trong folder con 'node_modules', dưới folder chứa index.js một bậc

Waaaa, như vậy đúng là một khi không để đường dẫn đến file module một cách cụ thể, thì bạn Node.js chỉ mò mẫn trong đống paths nhà bạn ý thôi.

Lưu ý:

  1. Nếu không require() đến file, mà đến folder, thì Node.js sẽ gọi file index.js chứa trong folder được gọi.
  2. Đôi khi không muốn load file hoặc package, mà chỉ kiểm tra xem file hoặc package đó đã tồn tại (được cài hay chưa), thì có thể chỉ cần dùng require.resolve(something) là đủ.
  3. Đường dẫn có 2 loại:
    • Tương đối (relative path):
    • Bắt đầu với ./ --> tính từ folder hiện tại
    • Bắt đầu với ../--> tính từ folder trên folder hiện tại 1 bậc
    • Tuyệt đối (absolute path):
    • Bắt đầu với /

Về loading

Chưa rõ

Về wrapping, tại sao lại cần wrapping (đóng gói)?

Chưa hiểu hết. Tính sau

Về caching, tại sao lại cần caching?

Cũng chưa hiểu. Nhưng là vấn đề quan trọng. Đọc lại sau.

ngminhtrung 16-02-2018

Bình luận


White
{{ comment.user.name }}
Bỏ hay Hay
{{comment.like_count}}
Male avatar
{{ comment_error }}
Hủy
   

Hiển thị thử

Chỉnh sửa

White

ngminhtrung

31 bài viết.
15 người follow
Kipalog
{{userFollowed ? 'Following' : 'Follow'}}
Cùng một tác giả
White
25 5
Ghi chú: Tiêu đề hoàn toàn mang tính câu view. Bài copy từ blog của tác giả :) Tại sao lại có bài viết này? Một ngày đẹp giời tôi cần kiểm t...
ngminhtrung viết 2 tháng trước
25 5
White
4 11
Nói thực, tôi cũng không biết gì về (Link) cho đến hôm bị cô bạn (Linh Ngô) đè ra cài ngấu nghiến trên máy và bảo cái này là "bắt buộc" nếu dùng Ma...
ngminhtrung viết 2 tháng trước
4 11
White
4 0
Vẽ Spirograph bằng D3.js Chắc hồi trẻ con ai cũng đã từng một lần nghịch 1 cái thước "sáng tạo" tên là "Spirograph" (/ˈspīrəˌɡraf/). Khi ấy ta đặt...
ngminhtrung viết 1 tháng trước
4 0
Bài viết liên quan
White
18 1
Toán tử XOR có tính chất: + A XOR A = 0 + 0 XOR A = A Với tính chất này, có thể cài đặt bài toán sau với độ phức tạp O(N) về runtime, và với O(1)...
kiennt viết hơn 1 năm trước
18 1
White
1 1
Chào mọi người, hôm nay mình viết một bài TIL nhỏ về cách lấy độ phân giải của màn hình hiện tại đang sử dụng. xdpyinfo | grep dimensions Kết quả...
namtx viết 7 tháng trước
1 1
White
8 0
Lấy fake path của file trong html input Ngữ cảnh: em cần làm một cái nút tải ảnh lên có preview. GIải pháp đầu: Dùng (Link) đọc file ảnh thành ba...
Hoàng Duy viết gần 2 năm trước
8 0
{{like_count}}

kipalog

{{ comment_count }}

bình luận

{{liked ? "Đã kipalog" : "Kipalog"}}


White
{{userFollowed ? 'Following' : 'Follow'}}
31 bài viết.
15 người follow

 Đầu mục bài viết

Vẫn còn nữa! x

Kipalog vẫn còn rất nhiều bài viết hay và chủ đề thú vị chờ bạn khám phá!