我该如何在Lua中构建一个自包含的模块结构?

我正在为Love2D制作一个Lua库,其中包含相当多的内部子模块、类文件等。

我现在正在做的看起来像这样:

文件./libname/init.lua

lib.prefix = (...):match("(.-)[^%.]+$") .. "libname."
lib = {}

lib.class = require(lib.prefix .. "lib.class")
lib.types.Blah = require(lib.prefix .. "types.Blah")

return lib

文件./libname/types/Blah.lua

local Blah = lib.class()
...
return Blah

问题在于lib是一个全局变量,如果我把它变成一个局部变量,我就无法正确地构造子模块(例如Blah),因为它们不能再访问lib表。

这显然是一个精简的例子,但我认为它很好地展示了我的问题 - 我想把lib表变成局部的,并返回它,这样库的导入类似于lib = require "libs.libname",而不是在导入模块本身时将整个东西导入全局范围。这可能吗?

点赞
用户3204551
用户3204551

让我们从官方文档中获取相关段落:

require (modname)

[...]

一旦找到加载器,require 将用两个参数调用加载器:modname 和一个取决于加载器来源方式的额外值。(如果加载器来自文件,则此额外值为文件名。)如果加载器返回任何非 nil 值,则 require 将返回值赋给 package.loaded[modname]。如果加载器未返回非 nil 值且未为 package.loaded[modname] 分配任何值,则 require 将 true 赋值给此条目。无论如何,require 都将返回 package.loaded[modname] 的最终值。

因此,此类递归依赖项的模块的所需步骤是:

  1. 获取模块名称 modname(第一个未命名的可变参数)
  2. 加载所需的用于基本初始化的模块
  3. 如果模块在步骤 2 中完全设置,则返回(递归调用)
  4. package.loaded[modname] 下设置模块表
  5. 设置足够的支架以启动子模块
  6. require 子模块
  7. 进行其余的模块设置。
  8. 返回(由于步骤 4,无需返回任何内容)。
local _M, modname = {}, {...}[1]
local sub = require(modname..".sub")
if package.loaded[modname] then return end
package.loaded[modname] = _M
-- 填充 _M 所需的全部内容以设置更多的模块
_M.X = require(modname..".X")
-- 完成这个模块的其余设置,完成
-- 返回

这样,您就可以完全不创建全局变量(这是现代模块的正确方式)。


如果在设置主模块时不需要任何子模块,请考虑使用按需加载:

setmetatable(_M, {__index =
  function(t, k)
    t[k] = require(modname.."."..k)
    return t[k]
  end})

如果您的模块仅为外部使用而分成子模块,则可以将其全部放入主模块中,让它将适当的成员表登记为子模块(参见步骤 2)。

在这种情况下,子模块加载器仅 require 主模块并返回 nothing。

2014-08-24 14:34:05
用户312586
用户312586

我为这个概念编写了一份指南,您可以在这里找到:

http://kiki.to/blog/2014/03/30/a-guide-to-authoring-lua-modules/

为了解决您在问题中提到的特定问题,我会使用三个文件:core.lua用于共享状态,更改核心的“真实文件”以及init将所有内容组合起来。

./libname/core.lua 定义了 lib 作为一个本地的 bar。它不会定义 lib.types。它使用工具为其他文件设置了基础,比如设置前缀或实用的 class

local lib = {}

lib.prefix = (...):match("(.-)[^%.]+$") .. "libname."

lib.class = require(lib.prefix .. "lib.class")

return lib

“常规文件”,例如 ./libname/types/Blah.lua,使用这些工具,但不修改 lib

local lib = require 'core' -- 或者 libname.core 或使用当前文件夹技巧

local Blah = lib.class()
...
return Blah

init.lua 将所有内容绑定在一起:

local lib = require 'core' -- 或者 libname.core 或使用当前文件夹技巧

lib.types.Blah = require(lib.prefix .. "types.Blah")

return lib

在评论中提到的“当前文件夹技巧”在此处:http://kiki.to/blog/2014/04/12/rule-5-beware-of-multiple-files/#the-current_folder-trick

2014-08-28 15:44:07