【笔记】lua - 扩展 require 的探索:从调用 require 的文件相对路径加载模块(关键字:require current relate path)

发布于:2022-12-09 ⋅ 阅读:(647) ⋅ 点赞:(0)

如果习惯用其他模块加载机制的语言(如:c/c++、js)会发现lua的模块加载规则很奇怪,它始终是根据初始文件的路径来调用模块的。

e.g.

./main.lua
./lib/a.lua
./lib/b.lua

-- 初始文件
lua main.lua

-- main.lua (如果 main.lua 调用 a 模块需要如下写法)
local a = require("lib/a")

-- lib/a.lua (如果 a.lua 调用 b 模块需要如下写法)
local b = require("lib/b")
-- 而其他语言则是习惯在 lib/a.lua 中这样写:
-- local b = require("b")

介于上述的规则,下述情况就会出现麻烦:
我引入一个第三方模块 thirdy,这个文件又引用其他模块,其他模块又引用其他模块,…

./main.lua                   -- require("lib/thirdy/init")
./lib/thirdy/init.lua        -- require("a"), require("b"), ...
./lib/thirdy/a.lua           -- require("b"), require("c"), ...
./lib/thirdy/b.lua           -- require("c"), ...
./lib/thirdy/c.lua
...

就这种情况我直接调用 main.lua 肯定是会报错,说找不到 “a”, “b”, “c” … 模块的

lua main.lua

除非我们把 “a”, “b”, “c” … 模块移到根目录下。但这样根目录文件会一大堆不好管理

./main.lua                   -- require("lib/thirdy/init")
./lib/thirdy/init.lua        -- require("a"), require("b"), ...
./a.lua                      -- require("b"), require("c"), ...
./b.lua                      -- require("c"), ...
./c.lua
...

又不可能手动的逐个修改第三方代码的 require(一般也不希望修改第三方代码)

./main.lua                   -- require("lib/thirdy/init")
./lib/thirdy/init.lua        -- require("lib/thirdy/a"), require("lib/thirdy/b"), ...
./lib/thirdy/a.lua           -- require("lib/thirdy/b"), require("lib/thirdy/c"), ...
./lib/thirdy/b.lua           -- require("lib/thirdy/c"), ...
./lib/thirdy/c.lua
...

有一个不太灵活的方法,在 main.lua 修改 package.path 使其包含模块所在目录

package.path = package.path .. ";./lib/thirdy/?.lua"

但这样不太灵活:每次新加模块都要在 main.lua 中添加目录

package.path = package.path .. ";./lib/thirdy-string/?.lua"
package.path = package.path .. ";./lib/thirdy-network/?.lua"
package.path = package.path .. ";./lib/thirdy-drawing/?.lua"
package.path = package.path .. ";./lib/thirdy-3d/?.lua"
...

有没有好点的方法呢?百度,谷歌实在没找到解决方案,全是复制粘贴相似内容无法解决问题。

那就自己写一个解决方案吧(or参考框架?)

分析发现问题其实很简单,只需要在调用 require 的时候尝试找下调用文件所在目录下是否有需要的模块即可:

ext_require.lua

return function(_require)
  _require = _require or require
  return function(modulename)
    local call_file_path = debug.getinfo(2, "S").short_src
    local call_dir_path = string.gsub(call_file_path, "(.*)[\\/][%w-_]+.lua", "%1")
    local ext_pattern = ";" .. call_dir_path .. "/?.lua"
    local _path = package.path
    local flag = not string.find(_path, ext_pattern, 1, true)
    if(flag) then
      package.path = _path ..  ext_pattern
    end
    local result = {_require(modulename)}
    if(flag) then
      package.path = _path
    end
    return table.unpack(result)
  end
end

调用

require = require("tools/ext_require")() -- 【全局定义】扩展require以获取请求文件所相对路径的文件

查看文档,发现 package.loaders(Lua5.2中, 改名为searchers) 控制着模块加载的规则:

(里面默认有四个方法。当 require 时, lua会根据其中的方法执行模块加载)

  1. preload:对已加载的module进行直接返回, 对应 package.preload[modname]

  2. lualoader:对lua文件进行加载, 搜索路径为 package.path

  3. cloader:对lua标准dll进行加载, 搜索路径为 package.cpath

  4. croot:官方文档说的是all-in-one加载器,它从 C 路径中查找指定模块的根名字

于是乎,要扩展 require 功能,只需要往里面添加一个方法:

table.insert(package.searchers, function(modulename)
  local call_file_path = debug.getinfo(3, "S").short_src
  local call_dir_path = string.gsub(call_file_path, "(.*)[\\/][%w-_]+.lua", "%1")
  local ext_pattern = call_dir_path .. "/" .. modulename .. ".lua"
  return loadfile(ext_pattern)
end)


CSDN话题挑战赛第2期
参赛话题:学习笔记


网站公告

今日签到

点亮在社区的每一天
去签到