Cải Tiến Quản Lý Mô-Đun Trong Lua - nói dối e blog

Cải Tiến Quản Lý Mô-Đun Trong Lua

Từ phiên bản 5.2, Lua đã đơn giản hóa cơ chế quản lý mô-đun so với phiên bản 5.1 và duy trì cách tiếp cận này cho đến nay. Khi tải mô-đun bằng hàm require, mỗi mô-đun cùng tên chỉ được tải duy nhất một lần trong một máy ảo (VM). Các lần gọi tiếp theo sẽ trả về kết quả đã tải trước đó. Quá trình tải mô-đun sử dụng các mẫu chuỗi được định nghĩa trong package.path hoặc package.cpath để chuyển đổi tên mô-đun thành đường dẫn tệp tin tương ứng và lần lượt thử mở các tệp tin này.

Trong dự án mới của tôi, do tích hợp nhiều mô-đun độc lập, tôi nhận thấy cơ chế hiện tại có một số hạn chế. Vì vậy, tôi đã thực hiện một cải tiến nhỏ để hỗ trợ cơ chế tương đối (relative) trong quản lý mô-đun, tương tự như cách Python xử lý. Khi một mô-đun sử dụng require để tải mô-đun khác, hệ thống sẽ ưu tiên tìm kiếm theo đường dẫn tương đối trước, sau đó mới thử với đường dẫn tuyệt đối. Điều này giúp dễ dàng tích hợp các mô-đun độc lập vào không gian tên (namespace) riêng và thuận tiện cho việc xây dựng mô-đun kiểm thử nội bộ.

Ví dụ minh họa:
Tôi phát triển độc lập một mô-đun tên foobar có chứa mô-đun con foobar.baz. Khi tích hợp vào hệ thống, tôi muốn đặt chúng vào không gian tên common. Thay vì sửa đổi mã nguồn mô-đun foobar để thay đổi các lệnh require "foobar.baz" thành require "common.foobar.baz" như trước đây, với cơ chế mới, tôi chỉ cần viết require "baz" trong mô-đun chính. Nếu tồn tại tệp baz.lua trong cùng thư mục, hệ thống sẽ ưu tiên tải tệp này. Các mô-đun bên ngoài vẫn có thể truy cập trực tiếp qua require "foobar.baz".

Để đảm bảo tính tương thích với cơ chế gốc, tôi không xây dựng hệ thống quản lý mô-đun hoàn toàn mới. Thay vào đó, tôi tận dụng thông tin tên mô-đun hiện tại được Lua truyền vào, và tạo một hàm bao (wrapper) cho require như sau:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
local loaded = package.loaded
local searchpath = package.searchpath
function import(modname)
  if modname then
    local prefix = modname:match "(.*%.).*$" or (modname .. ".")
    return function(name)
      local fullname = prefix .. name
      local m = loaded[fullname] or loaded[name]
      if m then
        return m
      end
      if searchpath(fullname, package.path) then
        return require(fullname)
      else
        return require(name)
      end
    end
  else
    return require
  end
end

Hàm import này sẽ tạo ra một hàm tải mô-đun với logic như đã mô tả. Tôi định nghĩa hàm này là biến toàn cục và thêm vào đầu dự án. Trong mỗi mô-đun, tôi thêm dòng:

1
local require = import and import(...) or require

Nếu import tồn tại, dòng này sẽ thay thế require gốc bằng phiên bản thông minh hơn; ngược lại vẫn giữ nguyên cơ chế gốc.

Ngoài ra, tôi ưa chuộng cách quản lý mô-đun theo cấu trúc thư mục. Ngay cả khi mô-đun chỉ gồm một tệp, tôi vẫn đặt nó trong thư mục riêng. Trong khi Lua mặc định sử dụng mẫu ?/init.lua để tìm tệp khởi tạo, tôi thích sử dụng mẫu ?/?.lua để trực tiếp lấy tên tệp trùng với tên thư mục làm điểm vào chính. Cách tiếp cận này giúp tổ chức dự án rõ ràng hơn, đặc biệt khi làm việc với các mô-đun con lồng ghép phức tạp.

0%