如果习惯用其他模块加载机制的语言(如: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会根据其中的方法执行模块加载)
preload:对已加载的module进行直接返回, 对应
package.preload[modname]
lualoader:对lua文件进行加载, 搜索路径为
package.path
cloader:对lua标准dll进行加载, 搜索路径为
package.cpath
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)
- 参考
- lua module/package 机制的自定义读取 - http://www.cppblog.com/sunicdavy/archive/2014/04/16/206610.html
- Lua 5.3 参考手册 - 模块 - http://cloudwu.github.io/lua53doc/manual.html#6.3
CSDN话题挑战赛第2期
参赛话题:学习笔记